| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
78bb6b0b1e
|
Revert "bugfix:Remove issue translation workflow as usthe/issues-translate-ac…"
This reverts commit
|
5 months ago |
|
|
018486951c
|
bugfix:Remove issue translation workflow as usthe/issues-translate-action@v2.7 does not allow issues in apache/incubator-seata-go (#821)
* refactor: implement pending TODOs in machine status_decision logic * feat(status-decision): add detailed logs to methods for better debugging * Remove issue translation workflow as usthe/issues-translate-action@v2.7 does not allow issues in apache/incubator-seata-go * bugfix: Remove issue translation workflow as usthe/issues-translate-action@v2.7 does not allow issues in apache/incubator-seata-go --------- Co-authored-by: FengZhang <zfcode@qq.com> |
5 months ago |
|
|
d51b344002
|
change the StateLangStore type (#812) | 5 months ago |
|
|
8e84b0ef2c
|
Feat cel expression (#788)
* implement cel expression * move sequence expression into a new file * mv expression factory into a new file * move expression factory manager into a new file * implement cel expression factory and add test * move expression resolver into a new file * add parse expression struct test * update * update go.mod --------- Co-authored-by: FengZhang <zfcode@qq.com> |
6 months ago |
|
|
0866141db3
|
refactor: implement pending TODOs in machine status_decision logic (#808)
* refactor: implement pending TODOs in machine status_decision logic * feat(status-decision): add detailed logs to methods for better debugging |
6 months ago |
|
|
d3a4cb1689
|
Feature/saga interface optimization (#778)
feature: saga Interface optimization |
6 months ago |
|
|
6c148cfb21
|
feat: Supplement the statelog_repository section in Database persistence for Saga state machine (#800)
* fix: Wrong commit * feat:add state_log_repository * fix: conflicts and wrong commit * fix: delete Remove redundant singleton patterns |
7 months ago |
|
|
689c5d6f7c
|
Feature: Database persistence for seata-go Saga state machine (#794)
* implement state machine repository * temporary recording * temporary recording * temporary storage * Supplementary test |
7 months ago |
|
|
bb1c1262d4
|
feat: HttpServiceTaskState Support (#769)
* support http invoker for sage engine * fix comment language |
8 months ago |
|
|
e70cabe8c7
|
[Refactor] Migrate StateMachineObject to client/config and unify config parsing with koanf (#785)
* optimize saga config reuse * upd-test * Modularize SagaConfig into independent configuration structure |
8 months ago |
|
|
6734025528
|
[to-reply] feature: support saga multi type config (#741)
* added yaml format of statelang related test data * feature: implement multi type config parser * adjust statemachine_json_parser to suit config parser * bugfix: fix the problem of failure to parse "ServiceName" * adjust statemachine_parser to be compatible with yaml * add apache License, add some comments, and make some logical adjustments * update state_machine_new_designer.yaml * optimize and improve the unit test of config parser * refactor config parser * add apache License * update |
9 months ago |
|
|
1385f9e856
|
feat: add func invoker (#744)
* add func task state |
11 months ago |
|
|
8d68ae4494
|
feature: sequential execution of state machine in Saga (#681) | 1 year ago |
|
|
d1fad744aa
|
feature saga :support generate id by Snowflake (#670) | 1 year ago |
|
|
540dab4ea4
|
feature: add default implementation for StateMachineConfig (#669) | 1 year ago |
|
|
90a8721983
|
feat: add grpc invoker in saga mode (#668)
* feat: add grpc invoker in saga mode * fix: fix some chores |
1 year ago |
|
|
33ff4e59cc
|
feature: add saga persistence layer (#649) | 1 year ago |
|
|
9c4c0f44a0
|
feature: add serverice task parse in statelang (#650) | 1 year ago |
|
|
34c5e527c0
|
refactor: refactor saga scaffold to break import cycles (#647) | 1 year ago |
|
|
58fc3e13df
|
feature: init saga framework (#635)
init saga framework |
1 year ago |
| @@ -25,7 +25,7 @@ require ( | |||||
| github.com/stretchr/testify v1.8.3 | github.com/stretchr/testify v1.8.3 | ||||
| go.uber.org/atomic v1.9.0 | go.uber.org/atomic v1.9.0 | ||||
| go.uber.org/zap v1.21.0 | go.uber.org/zap v1.21.0 | ||||
| google.golang.org/grpc v1.51.0 | |||||
| google.golang.org/grpc v1.57.0 | |||||
| gopkg.in/yaml.v2 v2.4.0 | gopkg.in/yaml.v2 v2.4.0 | ||||
| vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 | vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 | ||||
| ) | ) | ||||
| @@ -33,17 +33,23 @@ require ( | |||||
| require ( | require ( | ||||
| github.com/agiledragon/gomonkey v2.0.2+incompatible | github.com/agiledragon/gomonkey v2.0.2+incompatible | ||||
| github.com/agiledragon/gomonkey/v2 v2.9.0 | github.com/agiledragon/gomonkey/v2 v2.9.0 | ||||
| github.com/google/cel-go v0.18.0 | |||||
| github.com/mattn/go-sqlite3 v1.14.19 | |||||
| golang.org/x/sync v0.6.0 | |||||
| google.golang.org/protobuf v1.33.0 | |||||
| gopkg.in/yaml.v3 v3.0.1 | |||||
| ) | ) | ||||
| require ( | require ( | ||||
| github.com/RoaringBitmap/roaring v1.2.0 // indirect | github.com/RoaringBitmap/roaring v1.2.0 // indirect | ||||
| github.com/Workiva/go-datastructures v1.0.52 // indirect | github.com/Workiva/go-datastructures v1.0.52 // indirect | ||||
| github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect | |||||
| github.com/apache/dubbo-go-hessian2 v1.11.4 // indirect | github.com/apache/dubbo-go-hessian2 v1.11.4 // indirect | ||||
| github.com/benbjohnson/clock v1.1.0 // indirect | github.com/benbjohnson/clock v1.1.0 // indirect | ||||
| github.com/beorn7/perks v1.0.1 // indirect | github.com/beorn7/perks v1.0.1 // indirect | ||||
| github.com/bits-and-blooms/bitset v1.2.0 // indirect | github.com/bits-and-blooms/bitset v1.2.0 // indirect | ||||
| github.com/bytedance/sonic v1.9.1 // indirect | github.com/bytedance/sonic v1.9.1 // indirect | ||||
| github.com/cespare/xxhash/v2 v2.1.2 // indirect | |||||
| github.com/cespare/xxhash/v2 v2.3.0 // indirect | |||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect | ||||
| github.com/creasty/defaults v1.5.2 // indirect | github.com/creasty/defaults v1.5.2 // indirect | ||||
| github.com/davecgh/go-spew v1.1.1 // indirect | github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| @@ -52,7 +58,7 @@ require ( | |||||
| github.com/go-ole/go-ole v1.2.6 // indirect | github.com/go-ole/go-ole v1.2.6 // indirect | ||||
| github.com/go-playground/locales v0.14.1 // indirect | github.com/go-playground/locales v0.14.1 // indirect | ||||
| github.com/go-playground/universal-translator v0.18.1 // indirect | github.com/go-playground/universal-translator v0.18.1 // indirect | ||||
| github.com/golang/protobuf v1.5.2 // indirect | |||||
| github.com/golang/protobuf v1.5.4 // indirect | |||||
| github.com/golang/snappy v0.0.4 // indirect | github.com/golang/snappy v0.0.4 // indirect | ||||
| github.com/gorilla/websocket v1.4.2 // indirect | github.com/gorilla/websocket v1.4.2 // indirect | ||||
| github.com/jinzhu/copier v0.3.5 // indirect | github.com/jinzhu/copier v0.3.5 // indirect | ||||
| @@ -73,10 +79,11 @@ require ( | |||||
| github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 // indirect | github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 // indirect | ||||
| github.com/pmezard/go-difflib v1.0.0 // indirect | github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect | ||||
| github.com/prometheus/client_model v0.2.0 // indirect | |||||
| github.com/prometheus/client_model v0.3.0 // indirect | |||||
| github.com/prometheus/procfs v0.7.3 // indirect | github.com/prometheus/procfs v0.7.3 // indirect | ||||
| github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect | github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect | ||||
| github.com/shirou/gopsutil/v3 v3.22.2 // indirect | github.com/shirou/gopsutil/v3 v3.22.2 // indirect | ||||
| github.com/stoewer/go-strcase v1.2.0 // indirect | |||||
| github.com/tklauser/go-sysconf v0.3.10 // indirect | github.com/tklauser/go-sysconf v0.3.10 // indirect | ||||
| github.com/tklauser/numcpus v0.4.0 // indirect | github.com/tklauser/numcpus v0.4.0 // indirect | ||||
| github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | ||||
| @@ -84,10 +91,11 @@ require ( | |||||
| github.com/yusufpapurcu/wmi v1.2.2 // indirect | github.com/yusufpapurcu/wmi v1.2.2 // indirect | ||||
| go.uber.org/multierr v1.8.0 // indirect | go.uber.org/multierr v1.8.0 // indirect | ||||
| golang.org/x/arch v0.3.0 // indirect | golang.org/x/arch v0.3.0 // indirect | ||||
| golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect | |||||
| golang.org/x/text v0.14.0 // indirect | golang.org/x/text v0.14.0 // indirect | ||||
| google.golang.org/protobuf v1.30.0 // indirect | |||||
| google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect | |||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect | |||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect | ||||
| gopkg.in/yaml.v3 v3.0.1 // indirect | |||||
| ) | ) | ||||
| require ( | require ( | ||||
| @@ -98,10 +106,9 @@ require ( | |||||
| github.com/mattn/go-isatty v0.0.19 // indirect | github.com/mattn/go-isatty v0.0.19 // indirect | ||||
| github.com/pelletier/go-toml v1.9.3 // indirect | github.com/pelletier/go-toml v1.9.3 // indirect | ||||
| github.com/pingcap/log v0.0.0-20210906054005-afc726e70354 // indirect | github.com/pingcap/log v0.0.0-20210906054005-afc726e70354 // indirect | ||||
| golang.org/x/crypto v0.17.0 // indirect | |||||
| golang.org/x/net v0.10.0 // indirect | |||||
| golang.org/x/sys v0.15.0 // indirect | |||||
| google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7 // indirect | |||||
| golang.org/x/crypto v0.19.0 // indirect | |||||
| golang.org/x/net v0.21.0 // indirect | |||||
| golang.org/x/sys v0.17.0 // indirect | |||||
| vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d // indirect | vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d // indirect | ||||
| ) | ) | ||||
| @@ -67,6 +67,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 | |||||
| github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk= | github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk= | ||||
| github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= | github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= | ||||
| github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= | ||||
| github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= | |||||
| github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= | |||||
| github.com/apache/dubbo-getty v1.4.9-0.20221022181821-4dc6252ce98c/go.mod h1:6qmrqBSPGs3B35zwEuGhEYNVsx1nfGT/xzV2yOt2amM= | github.com/apache/dubbo-getty v1.4.9-0.20221022181821-4dc6252ce98c/go.mod h1:6qmrqBSPGs3B35zwEuGhEYNVsx1nfGT/xzV2yOt2amM= | ||||
| github.com/apache/dubbo-getty v1.4.10-0.20230731065302-7c0f0039e59c h1:e1pJKY0lFvO6rik7m3qmpMRA98cc9Zkg6AJeB1/7QFQ= | github.com/apache/dubbo-getty v1.4.10-0.20230731065302-7c0f0039e59c h1:e1pJKY0lFvO6rik7m3qmpMRA98cc9Zkg6AJeB1/7QFQ= | ||||
| github.com/apache/dubbo-getty v1.4.10-0.20230731065302-7c0f0039e59c/go.mod h1:TqHfi87Ufv7wpwI7nER5Kx8FCb/jjwlyazxiYwEmTs8= | github.com/apache/dubbo-getty v1.4.10-0.20230731065302-7c0f0039e59c/go.mod h1:TqHfi87Ufv7wpwI7nER5Kx8FCb/jjwlyazxiYwEmTs8= | ||||
| @@ -119,8 +121,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA | |||||
| github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= | github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= | ||||
| github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= | |||||
| github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | |||||
| github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | |||||
| github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | ||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= | ||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | ||||
| @@ -304,14 +307,17 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD | |||||
| github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||
| github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | |||||
| github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||
| github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= | |||||
| github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= | |||||
| github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||
| github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||
| github.com/google/cel-go v0.18.0 h1:u74MPiEC8mejBrkXqrTWT102g5IFEUjxOngzQIijMzU= | |||||
| github.com/google/cel-go v0.18.0/go.mod h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4= | |||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| @@ -515,6 +521,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k | |||||
| github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= | ||||
| github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||
| github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||
| github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= | |||||
| github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | |||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= | ||||
| @@ -635,8 +643,9 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1: | |||||
| github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | |||||
| github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= | |||||
| github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= | |||||
| github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= | ||||
| github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| @@ -706,6 +715,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn | |||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= | ||||
| github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= | |||||
| github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= | |||||
| github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | ||||
| github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | ||||
| github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= | ||||
| @@ -829,8 +840,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh | |||||
| golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= | |||||
| golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= | |||||
| golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= | |||||
| golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= | |||||
| golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| @@ -846,6 +857,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 | |||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||
| golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= | golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= | ||||
| golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= | |||||
| golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= | |||||
| golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| @@ -919,8 +932,8 @@ golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qx | |||||
| golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= | |||||
| golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | |||||
| golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= | |||||
| golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | |||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @@ -940,6 +953,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ | |||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | |||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | |||||
| golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @@ -1017,8 +1032,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc | |||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= | |||||
| golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | |||||
| golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= | |||||
| golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | |||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @@ -1167,8 +1182,10 @@ google.golang.org/genproto v0.0.0-20210106152847-07624b53cd92/go.mod h1:FWY/as6D | |||||
| google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= | ||||
| google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= | google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= | ||||
| google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= | google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= | ||||
| google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7 h1:q4zUJDd0+knPFB9x20S3vnxzlYNBbt8Yd7zBMVMteeM= | |||||
| google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= | |||||
| google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= | |||||
| google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= | |||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= | |||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= | |||||
| google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= | ||||
| @@ -1197,10 +1214,10 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ | |||||
| google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= | ||||
| google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= | google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= | ||||
| google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | ||||
| google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | |||||
| google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | ||||
| google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= | |||||
| google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= | google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= | ||||
| google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= | |||||
| google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= | |||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||
| @@ -1216,8 +1233,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ | |||||
| google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | ||||
| google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | ||||
| google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= | |||||
| google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | |||||
| google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= | |||||
| google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | |||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||
| gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= | gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| @@ -20,6 +20,7 @@ package client | |||||
| import ( | import ( | ||||
| "flag" | "flag" | ||||
| "fmt" | "fmt" | ||||
| "github.com/seata/seata-go/pkg/saga" | |||||
| "io/ioutil" | "io/ioutil" | ||||
| "os" | "os" | ||||
| "path/filepath" | "path/filepath" | ||||
| @@ -84,6 +85,8 @@ type Config struct { | |||||
| TransportConfig remoteConfig.TransportConfig `yaml:"transport" json:"transport" koanf:"transport"` | TransportConfig remoteConfig.TransportConfig `yaml:"transport" json:"transport" koanf:"transport"` | ||||
| ServiceConfig discovery.ServiceConfig `yaml:"service" json:"service" koanf:"service"` | ServiceConfig discovery.ServiceConfig `yaml:"service" json:"service" koanf:"service"` | ||||
| RegistryConfig discovery.RegistryConfig `yaml:"registry" json:"registry" koanf:"registry"` | RegistryConfig discovery.RegistryConfig `yaml:"registry" json:"registry" koanf:"registry"` | ||||
| SagaConfig saga.Config `yaml:"saga" json:"saga" koanf:"saga"` | |||||
| } | } | ||||
| func (c *Config) RegisterFlags(f *flag.FlagSet) { | func (c *Config) RegisterFlags(f *flag.FlagSet) { | ||||
| @@ -102,6 +105,7 @@ func (c *Config) RegisterFlags(f *flag.FlagSet) { | |||||
| c.TransportConfig.RegisterFlagsWithPrefix("transport", f) | c.TransportConfig.RegisterFlagsWithPrefix("transport", f) | ||||
| c.RegistryConfig.RegisterFlagsWithPrefix("registry", f) | c.RegistryConfig.RegisterFlagsWithPrefix("registry", f) | ||||
| c.ServiceConfig.RegisterFlagsWithPrefix("service", f) | c.ServiceConfig.RegisterFlagsWithPrefix("service", f) | ||||
| c.SagaConfig.RegisterFlagsWithPrefix("saga", f) | |||||
| } | } | ||||
| type loaderConf struct { | type loaderConf struct { | ||||
| @@ -37,6 +37,8 @@ func Select(loadBalanceType string, sessions *sync.Map, xid string) getty.Sessio | |||||
| return RandomLoadBalance(sessions, xid) | return RandomLoadBalance(sessions, xid) | ||||
| case xidLoadBalance: | case xidLoadBalance: | ||||
| return XidLoadBalance(sessions, xid) | return XidLoadBalance(sessions, xid) | ||||
| case roundRobinLoadBalance: | |||||
| return RoundRobinLoadBalance(sessions, xid) | |||||
| default: | default: | ||||
| return RandomLoadBalance(sessions, xid) | return RandomLoadBalance(sessions, xid) | ||||
| } | } | ||||
| @@ -0,0 +1,69 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package loadbalance | |||||
| import ( | |||||
| "math" | |||||
| "sort" | |||||
| "sync" | |||||
| "sync/atomic" | |||||
| getty "github.com/apache/dubbo-getty" | |||||
| ) | |||||
| var sequence int32 | |||||
| func RoundRobinLoadBalance(sessions *sync.Map, s string) getty.Session { | |||||
| // collect sync.Map adderToSession | |||||
| // filter out closed session instance | |||||
| adderToSession := make(map[string]getty.Session, 0) | |||||
| // map has no sequence, we should sort it to make sure the sequence is always the same | |||||
| adders := make([]string, 0) | |||||
| sessions.Range(func(key, value interface{}) bool { | |||||
| session := key.(getty.Session) | |||||
| if session.IsClosed() { | |||||
| sessions.Delete(key) | |||||
| } else { | |||||
| adderToSession[session.RemoteAddr()] = session | |||||
| adders = append(adders, session.RemoteAddr()) | |||||
| } | |||||
| return true | |||||
| }) | |||||
| sort.Strings(adders) | |||||
| // adderToSession eq 0 means there are no available session | |||||
| if len(adderToSession) == 0 { | |||||
| return nil | |||||
| } | |||||
| index := getPositiveSequence() % len(adderToSession) | |||||
| return adderToSession[adders[index]] | |||||
| } | |||||
| func getPositiveSequence() int { | |||||
| for { | |||||
| current := atomic.LoadInt32(&sequence) | |||||
| var next int32 | |||||
| if current == math.MaxInt32 { | |||||
| next = 0 | |||||
| } else { | |||||
| next = current + 1 | |||||
| } | |||||
| if atomic.CompareAndSwapInt32(&sequence, current, next) { | |||||
| return int(current) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package loadbalance | |||||
| import ( | |||||
| "fmt" | |||||
| "math" | |||||
| "sync" | |||||
| "testing" | |||||
| "github.com/golang/mock/gomock" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "github.com/seata/seata-go/pkg/remoting/mock" | |||||
| ) | |||||
| func TestRoundRobinLoadBalance_Normal(t *testing.T) { | |||||
| ctrl := gomock.NewController(t) | |||||
| sessions := &sync.Map{} | |||||
| for i := 0; i < 10; i++ { | |||||
| session := mock.NewMockTestSession(ctrl) | |||||
| session.EXPECT().IsClosed().Return(i == 2).AnyTimes() | |||||
| session.EXPECT().RemoteAddr().Return(fmt.Sprintf("%d", i)).AnyTimes() | |||||
| sessions.Store(session, fmt.Sprintf("session-%d", i+1)) | |||||
| } | |||||
| for i := 0; i < 10; i++ { | |||||
| if i == 2 { | |||||
| continue | |||||
| } | |||||
| result := RoundRobinLoadBalance(sessions, "some_xid") | |||||
| assert.Equal(t, fmt.Sprintf("%d", i), result.RemoteAddr()) | |||||
| assert.NotNil(t, result) | |||||
| assert.False(t, result.IsClosed()) | |||||
| } | |||||
| } | |||||
| func TestRoundRobinLoadBalance_OverSequence(t *testing.T) { | |||||
| ctrl := gomock.NewController(t) | |||||
| sessions := &sync.Map{} | |||||
| sequence = math.MaxInt32 | |||||
| for i := 0; i < 10; i++ { | |||||
| session := mock.NewMockTestSession(ctrl) | |||||
| session.EXPECT().IsClosed().Return(false).AnyTimes() | |||||
| session.EXPECT().RemoteAddr().Return(fmt.Sprintf("%d", i)).AnyTimes() | |||||
| sessions.Store(session, fmt.Sprintf("session-%d", i+1)) | |||||
| } | |||||
| for i := 0; i < 10; i++ { | |||||
| // over sequence here | |||||
| if i == 0 { | |||||
| result := RoundRobinLoadBalance(sessions, "some_xid") | |||||
| assert.Equal(t, "7", result.RemoteAddr()) | |||||
| assert.NotNil(t, result) | |||||
| assert.False(t, result.IsClosed()) | |||||
| continue | |||||
| } | |||||
| result := RoundRobinLoadBalance(sessions, "some_xid") | |||||
| assert.Equal(t, fmt.Sprintf("%d", i-1), result.RemoteAddr()) | |||||
| assert.NotNil(t, result) | |||||
| assert.False(t, result.IsClosed()) | |||||
| } | |||||
| } | |||||
| func TestRoundRobinLoadBalance_All_Closed(t *testing.T) { | |||||
| ctrl := gomock.NewController(t) | |||||
| sessions := &sync.Map{} | |||||
| for i := 0; i < 10; i++ { | |||||
| session := mock.NewMockTestSession(ctrl) | |||||
| session.EXPECT().IsClosed().Return(true).AnyTimes() | |||||
| sessions.Store(session, fmt.Sprintf("session-%d", i+1)) | |||||
| } | |||||
| if result := RoundRobinLoadBalance(sessions, "some_xid"); result != nil { | |||||
| t.Errorf("Expected nil, actual got %+v", result) | |||||
| } | |||||
| } | |||||
| func TestRoundRobinLoadBalance_Empty(t *testing.T) { | |||||
| sessions := &sync.Map{} | |||||
| if result := RoundRobinLoadBalance(sessions, "some_xid"); result != nil { | |||||
| t.Errorf("Expected nil, actual got %+v", result) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package saga | |||||
| import ( | |||||
| "flag" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine" | |||||
| ) | |||||
| type Config struct { | |||||
| StateMachine *statemachine.StateMachineObject `yaml:"state-machine" json:"state-machine" koanf:"state-machine"` | |||||
| } | |||||
| func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { | |||||
| if cfg.StateMachine != nil { | |||||
| cfg.StateMachine.RegisterFlagsWithPrefix(prefix+".state-machine", f) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| # seata saga | |||||
| 未来计划有三种使用方式 | |||||
| - 基于状态机引擎的 json | |||||
| link: statemachine_engine#Start | |||||
| - stream builder | |||||
| stateMachine.serviceTask().build().Start | |||||
| - 二阶段方式saga,类似tcc使用 | |||||
| 上面1、2是以来[statemachine](statemachine),状态机引擎实现的,3相对比较独立。 | |||||
| 状态机的实现在:saga-statemachine包中 | |||||
| 其中[statelang](statemachine%2Fstatelang)是状态机语言的解析,目前实现的是json解析方式,状态机语言可以参考: | |||||
| https://seata.io/docs/user/mode/saga | |||||
| 状态机json执行的入口类是:[statemachine_engine.go](statemachine%2Fengine%2Fstatemachine_engine.go) | |||||
| 下面简单说下engine中各个包的作用: | |||||
| events:saga的是基于事件处理的,其中是event、eventBus的实现 | |||||
| expr:表达式声明、解析、执行 | |||||
| invoker:声明了serviceInvoker、scriptInvoker等接口、task调用管理、执行都在这个包中,例如httpInvoker | |||||
| process_ctrl:状态机处理流程:上下文、执行、事件流转 | |||||
| sequence:分布式id | |||||
| store:状态机存储接口、实现 | |||||
| status_decision:状态机状态决策 | |||||
| @@ -0,0 +1,103 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package constant | |||||
| type NetExceptionType string | |||||
| const ( | |||||
| // region State Types | |||||
| StateTypeServiceTask string = "ServiceTask" | |||||
| StateTypeChoice string = "Choice" | |||||
| StateTypeFail string = "Fail" | |||||
| StateTypeSucceed string = "Succeed" | |||||
| StateTypeCompensationTrigger string = "CompensationTrigger" | |||||
| StateTypeSubStateMachine string = "SubStateMachine" | |||||
| StateTypeCompensateSubMachine string = "CompensateSubMachine" | |||||
| StateTypeScriptTask string = "ScriptTask" | |||||
| StateTypeLoopStart string = "LoopStart" | |||||
| // end region | |||||
| // region Service Types | |||||
| ServiceTypeGRPC string = "GRPC" | |||||
| // end region | |||||
| // region System Variables | |||||
| VarNameOutputParams string = "outputParams" | |||||
| VarNameProcessType string = "_ProcessType_" | |||||
| VarNameOperationName string = "_operation_name_" | |||||
| OperationNameStart string = "start" | |||||
| OperationNameCompensate string = "compensate" | |||||
| VarNameAsyncCallback string = "_async_callback_" | |||||
| VarNameCurrentExceptionRoute string = "_current_exception_route_" | |||||
| VarNameIsExceptionNotCatch string = "_is_exception_not_catch_" | |||||
| VarNameSubMachineParentId string = "_sub_machine_parent_id_" | |||||
| VarNameCurrentChoice string = "_current_choice_" | |||||
| VarNameStateMachineInst string = "_current_statemachine_instance_" | |||||
| VarNameStateMachine string = "_current_statemachine_" | |||||
| VarNameStateMachineEngine string = "_current_statemachine_engine_" | |||||
| VarNameStateMachineConfig string = "_statemachine_config_" | |||||
| VarNameStateMachineContext string = "context" | |||||
| VarNameIsAsyncExecution string = "_is_async_execution_" | |||||
| VarNameStateInst string = "_current_state_instance_" | |||||
| VarNameBusinesskey string = "_business_key_" | |||||
| VarNameParentId string = "_parent_id_" | |||||
| VarNameCurrentException string = "currentException" | |||||
| CompensateSubMachineStateNamePrefix string = "_compensate_sub_machine_state_" | |||||
| DefaultScriptType string = "groovy" | |||||
| VarNameSyncExeStack string = "_sync_execution_stack_" | |||||
| VarNameInputParams string = "inputParams" | |||||
| VarNameIsLoopState string = "_is_loop_state_" | |||||
| VarNameCurrentCompensateTriggerState string = "_is_compensating_" | |||||
| VarNameCurrentCompensationHolder string = "_current_compensation_holder_" | |||||
| VarNameFirstCompensationStateStarted string = "_first_compensation_state_started" | |||||
| VarNameCurrentLoopContextHolder string = "_current_loop_context_holder_" | |||||
| VarNameRetriedStateInstId string = "_retried_state_instance_id" | |||||
| VarNameIsForSubStatMachineForward string = "_is_for_sub_statemachine_forward_" | |||||
| // TODO: this lock in process context only has one, try to add more to add concurrent | |||||
| VarNameProcessContextMutexLock string = "_current_context_mutex_lock" | |||||
| VarNameFailEndStateFlag string = "_fail_end_state_flag_" | |||||
| VarNameGlobalTx string = "_global_transaction_" | |||||
| // end region | |||||
| // region of loop | |||||
| LoopCounter string = "loopCounter" | |||||
| LoopSemaphore string = "loopSemaphore" | |||||
| LoopResult string = "loopResult" | |||||
| NumberOfInstances string = "nrOfInstances" | |||||
| NumberOfActiveInstances string = "nrOfActiveInstances" | |||||
| NumberOfCompletedInstances string = "nrOfCompletedInstances" | |||||
| // end region | |||||
| // region others | |||||
| SeqEntityStateMachine string = "STATE_MACHINE" | |||||
| SeqEntityStateMachineInst string = "STATE_MACHINE_INST" | |||||
| SeqEntityStateInst string = "STATE_INST" | |||||
| OperationNameForward string = "forward" | |||||
| LoopStateNamePattern string = "-loop-" | |||||
| SagaTransNamePrefix string = "$Saga_" | |||||
| // end region | |||||
| SeperatorParentId string = ":" | |||||
| // Machine execution timeout error code | |||||
| FrameworkErrorCodeStateMachineExecutionTimeout = "StateMachineExecutionTimeout" | |||||
| ConnectException NetExceptionType = "CONNECT_EXCEPTION" | |||||
| ConnectTimeoutException NetExceptionType = "CONNECT_TIMEOUT_EXCEPTION" | |||||
| ReadTimeoutException NetExceptionType = "READ_TIMEOUT_EXCEPTION" | |||||
| NotNetException NetExceptionType = "NOT_NET_EXCEPTION" | |||||
| ) | |||||
| @@ -0,0 +1,102 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||||
| "sync" | |||||
| ) | |||||
| type BusinessProcessor interface { | |||||
| Process(ctx context.Context, processContext ProcessContext) error | |||||
| Route(ctx context.Context, processContext ProcessContext) error | |||||
| } | |||||
| type DefaultBusinessProcessor struct { | |||||
| processHandlers map[string]ProcessHandler | |||||
| routerHandlers map[string]RouterHandler | |||||
| mu sync.RWMutex | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) RegistryProcessHandler(processType process.ProcessType, processHandler ProcessHandler) { | |||||
| d.mu.Lock() | |||||
| defer d.mu.Unlock() | |||||
| d.processHandlers[string(processType)] = processHandler | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) RegistryRouterHandler(processType process.ProcessType, routerHandler RouterHandler) { | |||||
| d.mu.Lock() | |||||
| defer d.mu.Unlock() | |||||
| d.routerHandlers[string(processType)] = routerHandler | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) Process(ctx context.Context, processContext ProcessContext) error { | |||||
| processType := d.matchProcessType(processContext) | |||||
| processHandler, err := d.getProcessHandler(processType) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return processHandler.Process(ctx, processContext) | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) Route(ctx context.Context, processContext ProcessContext) error { | |||||
| processType := d.matchProcessType(processContext) | |||||
| routerHandler, err := d.getRouterHandler(processType) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return routerHandler.Route(ctx, processContext) | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) getProcessHandler(processType process.ProcessType) (ProcessHandler, error) { | |||||
| d.mu.RLock() | |||||
| defer d.mu.RUnlock() | |||||
| processHandler, ok := d.processHandlers[string(processType)] | |||||
| if !ok { | |||||
| return nil, errors.New("Cannot find Process handler by type " + string(processType)) | |||||
| } | |||||
| return processHandler, nil | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) getRouterHandler(processType process.ProcessType) (RouterHandler, error) { | |||||
| d.mu.RLock() | |||||
| defer d.mu.RUnlock() | |||||
| routerHandler, ok := d.routerHandlers[string(processType)] | |||||
| if !ok { | |||||
| return nil, errors.New("Cannot find router handler by type " + string(processType)) | |||||
| } | |||||
| return routerHandler, nil | |||||
| } | |||||
| func (d *DefaultBusinessProcessor) matchProcessType(processContext ProcessContext) process.ProcessType { | |||||
| ok := processContext.HasVariable(constant.VarNameProcessType) | |||||
| if ok { | |||||
| return processContext.GetVariable(constant.VarNameProcessType).(process.ProcessType) | |||||
| } | |||||
| return process.StateLang | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/util/collection" | |||||
| "sync" | |||||
| ) | |||||
| type CompensationHolder struct { | |||||
| statesNeedCompensation *sync.Map | |||||
| statesForCompensation *sync.Map | |||||
| stateStackNeedCompensation *collection.Stack | |||||
| } | |||||
| func (c *CompensationHolder) StatesNeedCompensation() *sync.Map { | |||||
| return c.statesNeedCompensation | |||||
| } | |||||
| func (c *CompensationHolder) SetStatesNeedCompensation(statesNeedCompensation *sync.Map) { | |||||
| c.statesNeedCompensation = statesNeedCompensation | |||||
| } | |||||
| func (c *CompensationHolder) StatesForCompensation() *sync.Map { | |||||
| return c.statesForCompensation | |||||
| } | |||||
| func (c *CompensationHolder) SetStatesForCompensation(statesForCompensation *sync.Map) { | |||||
| c.statesForCompensation = statesForCompensation | |||||
| } | |||||
| func (c *CompensationHolder) StateStackNeedCompensation() *collection.Stack { | |||||
| return c.stateStackNeedCompensation | |||||
| } | |||||
| func (c *CompensationHolder) SetStateStackNeedCompensation(stateStackNeedCompensation *collection.Stack) { | |||||
| c.stateStackNeedCompensation = stateStackNeedCompensation | |||||
| } | |||||
| func (c *CompensationHolder) AddToBeCompensatedState(stateName string, toBeCompensatedState statelang.StateInstance) { | |||||
| c.statesNeedCompensation.Store(stateName, toBeCompensatedState) | |||||
| } | |||||
| func NewCompensationHolder() *CompensationHolder { | |||||
| return &CompensationHolder{ | |||||
| statesNeedCompensation: &sync.Map{}, | |||||
| statesForCompensation: &sync.Map{}, | |||||
| stateStackNeedCompensation: collection.NewStack(), | |||||
| } | |||||
| } | |||||
| func GetCurrentCompensationHolder(ctx context.Context, processContext ProcessContext, forceCreate bool) *CompensationHolder { | |||||
| compensationholder := processContext.GetVariable(constant.VarNameCurrentCompensationHolder).(*CompensationHolder) | |||||
| lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||||
| lock.Lock() | |||||
| defer lock.Unlock() | |||||
| if compensationholder == nil && forceCreate { | |||||
| compensationholder = NewCompensationHolder() | |||||
| processContext.SetVariable(constant.VarNameCurrentCompensationHolder, compensationholder) | |||||
| } | |||||
| return compensationholder | |||||
| } | |||||
| @@ -0,0 +1,260 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||||
| "sync" | |||||
| ) | |||||
| const ( | |||||
| DefaultTransOperTimeout = 60000 * 30 | |||||
| DefaultServiceInvokeTimeout = 60000 * 5 | |||||
| DefaultClientSagaRetryPersistModeUpdate = false | |||||
| DefaultClientSagaCompensatePersistModeUpdate = false | |||||
| DefaultClientReportSuccessEnable = false | |||||
| DefaultClientSagaBranchRegisterEnable = true | |||||
| ) | |||||
| type DefaultStateMachineConfig struct { | |||||
| // Configuration | |||||
| transOperationTimeout int | |||||
| serviceInvokeTimeout int | |||||
| charset string | |||||
| defaultTenantId string | |||||
| sagaRetryPersistModeUpdate bool | |||||
| sagaCompensatePersistModeUpdate bool | |||||
| sagaBranchRegisterEnable bool | |||||
| rmReportSuccessEnable bool | |||||
| // Components | |||||
| // Event publisher | |||||
| syncProcessCtrlEventPublisher EventPublisher | |||||
| asyncProcessCtrlEventPublisher EventPublisher | |||||
| // Store related components | |||||
| stateLogRepository StateLogRepository | |||||
| stateLogStore StateLogStore | |||||
| stateLangStore StateLangStore | |||||
| stateMachineRepository StateMachineRepository | |||||
| // Expression related components | |||||
| expressionFactoryManager expr.ExpressionFactoryManager | |||||
| expressionResolver expr.ExpressionResolver | |||||
| // Invoker related components | |||||
| serviceInvokerManager invoker.ServiceInvokerManager | |||||
| scriptInvokerManager invoker.ScriptInvokerManager | |||||
| // Other components | |||||
| statusDecisionStrategy StatusDecisionStrategy | |||||
| seqGenerator sequence.SeqGenerator | |||||
| componentLock *sync.Mutex | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ComponentLock() *sync.Mutex { | |||||
| return c.componentLock | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetComponentLock(componentLock *sync.Mutex) { | |||||
| c.componentLock = componentLock | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetTransOperationTimeout(transOperationTimeout int) { | |||||
| c.transOperationTimeout = transOperationTimeout | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetServiceInvokeTimeout(serviceInvokeTimeout int) { | |||||
| c.serviceInvokeTimeout = serviceInvokeTimeout | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetCharset(charset string) { | |||||
| c.charset = charset | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetDefaultTenantId(defaultTenantId string) { | |||||
| c.defaultTenantId = defaultTenantId | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetSyncProcessCtrlEventPublisher(syncProcessCtrlEventPublisher EventPublisher) { | |||||
| c.syncProcessCtrlEventPublisher = syncProcessCtrlEventPublisher | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetAsyncProcessCtrlEventPublisher(asyncProcessCtrlEventPublisher EventPublisher) { | |||||
| c.asyncProcessCtrlEventPublisher = asyncProcessCtrlEventPublisher | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetStateLogRepository(stateLogRepository StateLogRepository) { | |||||
| c.stateLogRepository = stateLogRepository | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetStateLogStore(stateLogStore StateLogStore) { | |||||
| c.stateLogStore = stateLogStore | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetStateLangStore(stateLangStore StateLangStore) { | |||||
| c.stateLangStore = stateLangStore | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetStateMachineRepository(stateMachineRepository StateMachineRepository) { | |||||
| c.stateMachineRepository = stateMachineRepository | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetExpressionFactoryManager(expressionFactoryManager expr.ExpressionFactoryManager) { | |||||
| c.expressionFactoryManager = expressionFactoryManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetExpressionResolver(expressionResolver expr.ExpressionResolver) { | |||||
| c.expressionResolver = expressionResolver | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetServiceInvokerManager(serviceInvokerManager invoker.ServiceInvokerManager) { | |||||
| c.serviceInvokerManager = serviceInvokerManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetScriptInvokerManager(scriptInvokerManager invoker.ScriptInvokerManager) { | |||||
| c.scriptInvokerManager = scriptInvokerManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetStatusDecisionStrategy(statusDecisionStrategy StatusDecisionStrategy) { | |||||
| c.statusDecisionStrategy = statusDecisionStrategy | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetSeqGenerator(seqGenerator sequence.SeqGenerator) { | |||||
| c.seqGenerator = seqGenerator | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) StateLogRepository() StateLogRepository { | |||||
| return c.stateLogRepository | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) StateMachineRepository() StateMachineRepository { | |||||
| return c.stateMachineRepository | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) StateLogStore() StateLogStore { | |||||
| return c.stateLogStore | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) StateLangStore() StateLangStore { | |||||
| return c.stateLangStore | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ExpressionFactoryManager() expr.ExpressionFactoryManager { | |||||
| return c.expressionFactoryManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ExpressionResolver() expr.ExpressionResolver { | |||||
| return c.expressionResolver | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SeqGenerator() sequence.SeqGenerator { | |||||
| return c.seqGenerator | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) StatusDecisionStrategy() StatusDecisionStrategy { | |||||
| return c.statusDecisionStrategy | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) EventPublisher() EventPublisher { | |||||
| return c.syncProcessCtrlEventPublisher | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) AsyncEventPublisher() EventPublisher { | |||||
| return c.asyncProcessCtrlEventPublisher | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ServiceInvokerManager() invoker.ServiceInvokerManager { | |||||
| return c.serviceInvokerManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ScriptInvokerManager() invoker.ScriptInvokerManager { | |||||
| return c.scriptInvokerManager | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) CharSet() string { | |||||
| return c.charset | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetCharSet(charset string) { | |||||
| c.charset = charset | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) DefaultTenantId() string { | |||||
| return c.defaultTenantId | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) TransOperationTimeout() int { | |||||
| return c.transOperationTimeout | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) ServiceInvokeTimeout() int { | |||||
| return c.serviceInvokeTimeout | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) IsSagaRetryPersistModeUpdate() bool { | |||||
| return c.sagaRetryPersistModeUpdate | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetSagaRetryPersistModeUpdate(sagaRetryPersistModeUpdate bool) { | |||||
| c.sagaRetryPersistModeUpdate = sagaRetryPersistModeUpdate | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) IsSagaCompensatePersistModeUpdate() bool { | |||||
| return c.sagaCompensatePersistModeUpdate | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetSagaCompensatePersistModeUpdate(sagaCompensatePersistModeUpdate bool) { | |||||
| c.sagaCompensatePersistModeUpdate = sagaCompensatePersistModeUpdate | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) IsSagaBranchRegisterEnable() bool { | |||||
| return c.sagaBranchRegisterEnable | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetSagaBranchRegisterEnable(sagaBranchRegisterEnable bool) { | |||||
| c.sagaBranchRegisterEnable = sagaBranchRegisterEnable | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) IsRmReportSuccessEnable() bool { | |||||
| return c.rmReportSuccessEnable | |||||
| } | |||||
| func (c *DefaultStateMachineConfig) SetRmReportSuccessEnable(rmReportSuccessEnable bool) { | |||||
| c.rmReportSuccessEnable = rmReportSuccessEnable | |||||
| } | |||||
| func NewDefaultStateMachineConfig() *DefaultStateMachineConfig { | |||||
| c := &DefaultStateMachineConfig{ | |||||
| transOperationTimeout: DefaultTransOperTimeout, | |||||
| serviceInvokeTimeout: DefaultServiceInvokeTimeout, | |||||
| charset: "UTF-8", | |||||
| defaultTenantId: "000001", | |||||
| sagaRetryPersistModeUpdate: DefaultClientSagaRetryPersistModeUpdate, | |||||
| sagaCompensatePersistModeUpdate: DefaultClientSagaCompensatePersistModeUpdate, | |||||
| sagaBranchRegisterEnable: DefaultClientSagaBranchRegisterEnable, | |||||
| rmReportSuccessEnable: DefaultClientReportSuccessEnable, | |||||
| componentLock: &sync.Mutex{}, | |||||
| } | |||||
| // TODO: init config | |||||
| return c | |||||
| } | |||||
| @@ -0,0 +1,184 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| "golang.org/x/sync/semaphore" | |||||
| "reflect" | |||||
| "strings" | |||||
| "sync" | |||||
| "time" | |||||
| ) | |||||
| func EndStateMachine(ctx context.Context, processContext ProcessContext) error { | |||||
| if processContext.HasVariable(constant.VarNameIsLoopState) { | |||||
| if processContext.HasVariable(constant.LoopSemaphore) { | |||||
| weighted, ok := processContext.GetVariable(constant.LoopSemaphore).(semaphore.Weighted) | |||||
| if !ok { | |||||
| return errors.New("semaphore type is not weighted") | |||||
| } | |||||
| weighted.Release(1) | |||||
| } | |||||
| } | |||||
| stateMachineInstance, ok := processContext.GetVariable(constant.VarNameStateMachineInst).(statelang.StateMachineInstance) | |||||
| if !ok { | |||||
| return errors.New("state machine instance type is not statelang.StateMachineInstance") | |||||
| } | |||||
| stateMachineInstance.SetEndTime(time.Now()) | |||||
| exp, ok := processContext.GetVariable(constant.VarNameCurrentException).(error) | |||||
| if !ok { | |||||
| return errors.New("exception type is not error") | |||||
| } | |||||
| if exp != nil { | |||||
| stateMachineInstance.SetException(exp) | |||||
| log.Debugf("Exception Occurred: %s", exp) | |||||
| } | |||||
| stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||||
| if err := stateMachineConfig.StatusDecisionStrategy().DecideOnEndState(ctx, processContext, stateMachineInstance, exp); err != nil { | |||||
| return err | |||||
| } | |||||
| contextParams, ok := processContext.GetVariable(constant.VarNameStateMachineContext).(map[string]interface{}) | |||||
| if !ok { | |||||
| return errors.New("state machine context type is not map[string]interface{}") | |||||
| } | |||||
| endParams := stateMachineInstance.EndParams() | |||||
| for k, v := range contextParams { | |||||
| endParams[k] = v | |||||
| } | |||||
| stateMachineInstance.SetEndParams(endParams) | |||||
| stateInstruction, ok := processContext.GetInstruction().(StateInstruction) | |||||
| if !ok { | |||||
| return errors.New("state instruction type is not process_ctrl.StateInstruction") | |||||
| } | |||||
| stateInstruction.SetEnd(true) | |||||
| stateMachineInstance.SetRunning(false) | |||||
| stateMachineInstance.SetEndTime(time.Now()) | |||||
| if stateMachineInstance.StateMachine().IsPersist() && stateMachineConfig.StateLangStore() != nil { | |||||
| err := stateMachineConfig.StateLogStore().RecordStateMachineFinished(ctx, stateMachineInstance, processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| callBack, ok := processContext.GetVariable(constant.VarNameAsyncCallback).(CallBack) | |||||
| if ok { | |||||
| if exp != nil { | |||||
| callBack.OnError(ctx, processContext, stateMachineInstance, exp) | |||||
| } else { | |||||
| callBack.OnFinished(ctx, processContext, stateMachineInstance) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func HandleException(processContext ProcessContext, abstractTaskState *state.AbstractTaskState, err error) { | |||||
| catches := abstractTaskState.Catches() | |||||
| if catches != nil && len(catches) != 0 { | |||||
| for _, exceptionMatch := range catches { | |||||
| exceptions := exceptionMatch.Exceptions() | |||||
| exceptionTypes := exceptionMatch.ExceptionTypes() | |||||
| if exceptions != nil && len(exceptions) != 0 { | |||||
| if exceptionTypes == nil { | |||||
| lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||||
| lock.Lock() | |||||
| defer lock.Unlock() | |||||
| error := errors.New("") | |||||
| for i := 0; i < len(exceptions); i++ { | |||||
| exceptionTypes = append(exceptionTypes, reflect.TypeOf(error)) | |||||
| } | |||||
| } | |||||
| exceptionMatch.SetExceptionTypes(exceptionTypes) | |||||
| } | |||||
| for i, _ := range exceptionTypes { | |||||
| if reflect.TypeOf(err) == exceptionTypes[i] { | |||||
| // HACK: we can not get error type in config file during runtime, so we use exception str | |||||
| if strings.Contains(err.Error(), exceptions[i]) { | |||||
| hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||||
| hierarchicalProcessContext.SetVariable(constant.VarNameCurrentExceptionRoute, exceptionMatch.Next()) | |||||
| return | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| log.Error("Task execution failed and no catches configured") | |||||
| hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||||
| hierarchicalProcessContext.SetVariable(constant.VarNameIsExceptionNotCatch, true) | |||||
| } | |||||
| // GetOriginStateName get origin state name without suffix like fork | |||||
| func GetOriginStateName(stateInstance statelang.StateInstance) string { | |||||
| stateName := stateInstance.Name() | |||||
| if stateName != "" { | |||||
| end := strings.LastIndex(stateName, constant.LoopStateNamePattern) | |||||
| if end > -1 { | |||||
| return stateName[:end+1] | |||||
| } | |||||
| } | |||||
| return stateName | |||||
| } | |||||
| // IsTimeout test if is timeout | |||||
| func IsTimeout(gmtUpdated time.Time, timeoutMillis int) bool { | |||||
| if timeoutMillis < 0 { | |||||
| return false | |||||
| } | |||||
| return time.Now().Unix()-gmtUpdated.Unix() > int64(timeoutMillis) | |||||
| } | |||||
| func GenerateParentId(stateInstance statelang.StateInstance) string { | |||||
| return stateInstance.MachineInstanceID() + constant.SeperatorParentId + stateInstance.ID() | |||||
| } | |||||
| // GetNetExceptionType Speculate what kind of network anomaly is caused by the error | |||||
| func GetNetExceptionType(err error) constant.NetExceptionType { | |||||
| if err == nil { | |||||
| return constant.NotNetException | |||||
| } | |||||
| // If it contains a specific error message, simply guess | |||||
| errMsg := err.Error() | |||||
| if strings.Contains(errMsg, "connection refused") { | |||||
| return constant.ConnectException | |||||
| } else if strings.Contains(errMsg, "timeout") { | |||||
| return constant.ConnectTimeoutException | |||||
| } else if strings.Contains(errMsg, "i/o timeout") { | |||||
| return constant.ReadTimeoutException | |||||
| } | |||||
| return constant.NotNetException | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| type Event interface { | |||||
| } | |||||
| @@ -0,0 +1,130 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/util/collection" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type EventBus interface { | |||||
| Offer(ctx context.Context, event Event) (bool, error) | |||||
| EventConsumerList(event Event) []EventConsumer | |||||
| RegisterEventConsumer(consumer EventConsumer) | |||||
| } | |||||
| type BaseEventBus struct { | |||||
| eventConsumerList []EventConsumer | |||||
| } | |||||
| func (b *BaseEventBus) RegisterEventConsumer(consumer EventConsumer) { | |||||
| if b.eventConsumerList == nil { | |||||
| b.eventConsumerList = make([]EventConsumer, 0) | |||||
| } | |||||
| b.eventConsumerList = append(b.eventConsumerList, consumer) | |||||
| } | |||||
| func (b *BaseEventBus) EventConsumerList(event Event) []EventConsumer { | |||||
| var acceptedConsumerList = make([]EventConsumer, 0) | |||||
| for i := range b.eventConsumerList { | |||||
| eventConsumer := b.eventConsumerList[i] | |||||
| if eventConsumer.Accept(event) { | |||||
| acceptedConsumerList = append(acceptedConsumerList, eventConsumer) | |||||
| } | |||||
| } | |||||
| return acceptedConsumerList | |||||
| } | |||||
| type DirectEventBus struct { | |||||
| BaseEventBus | |||||
| } | |||||
| func (d DirectEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||||
| eventConsumerList := d.EventConsumerList(event) | |||||
| if len(eventConsumerList) == 0 { | |||||
| log.Debugf("cannot find event handler by type: %T", event) | |||||
| return false, nil | |||||
| } | |||||
| isFirstEvent := true | |||||
| processContext, ok := event.(ProcessContext) | |||||
| if !ok { | |||||
| log.Errorf("event %T is illegal, required process_ctrl.ProcessContext", event) | |||||
| return false, nil | |||||
| } | |||||
| stack := processContext.GetVariable(constant.VarNameSyncExeStack).(*collection.Stack) | |||||
| if stack == nil { | |||||
| stack = collection.NewStack() | |||||
| processContext.SetVariable(constant.VarNameSyncExeStack, stack) | |||||
| isFirstEvent = true | |||||
| } | |||||
| stack.Push(processContext) | |||||
| if isFirstEvent { | |||||
| for stack.Len() > 0 { | |||||
| currentContext := stack.Pop().(ProcessContext) | |||||
| for _, eventConsumer := range eventConsumerList { | |||||
| err := eventConsumer.Process(ctx, currentContext) | |||||
| if err != nil { | |||||
| log.Errorf("process event %T error: %s", event, err.Error()) | |||||
| return false, err | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return true, nil | |||||
| } | |||||
| type AsyncEventBus struct { | |||||
| BaseEventBus | |||||
| } | |||||
| func (a AsyncEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||||
| eventConsumerList := a.EventConsumerList(event) | |||||
| if len(eventConsumerList) == 0 { | |||||
| errStr := fmt.Sprintf("cannot find event handler by type: %T", event) | |||||
| log.Errorf(errStr) | |||||
| return false, errors.New(errStr) | |||||
| } | |||||
| processContext, ok := event.(ProcessContext) | |||||
| if !ok { | |||||
| errStr := fmt.Sprintf("event %T is illegal, required process_ctrl.ProcessContext", event) | |||||
| log.Errorf(errStr) | |||||
| return false, errors.New(errStr) | |||||
| } | |||||
| for _, eventConsumer := range eventConsumerList { | |||||
| go func() { | |||||
| err := eventConsumer.Process(ctx, processContext) | |||||
| if err != nil { | |||||
| log.Errorf("process event %T error: %s", event, err.Error()) | |||||
| } | |||||
| }() | |||||
| } | |||||
| return true, nil | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| ) | |||||
| type EventConsumer interface { | |||||
| Accept(event Event) bool | |||||
| Process(ctx context.Context, event Event) error | |||||
| } | |||||
| type ProcessCtrlEventConsumer struct { | |||||
| processController ProcessController | |||||
| } | |||||
| func (p ProcessCtrlEventConsumer) Accept(event Event) bool { | |||||
| if event == nil { | |||||
| return false | |||||
| } | |||||
| _, ok := event.(ProcessContext) | |||||
| return ok | |||||
| } | |||||
| func (p ProcessCtrlEventConsumer) Process(ctx context.Context, event Event) error { | |||||
| processContext, ok := event.(ProcessContext) | |||||
| if !ok { | |||||
| return errors.New(fmt.Sprint("event %T is illegal, required process_ctrl.ProcessContext", event)) | |||||
| } | |||||
| return p.processController.Process(ctx, processContext) | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import "context" | |||||
| type EventPublisher interface { | |||||
| PushEvent(ctx context.Context, event Event) (bool, error) | |||||
| } | |||||
| type ProcessCtrlEventPublisher struct { | |||||
| eventBus EventBus | |||||
| } | |||||
| func NewProcessCtrlEventPublisher(eventBus EventBus) *ProcessCtrlEventPublisher { | |||||
| return &ProcessCtrlEventPublisher{eventBus: eventBus} | |||||
| } | |||||
| func (p ProcessCtrlEventPublisher) PushEvent(ctx context.Context, event Event) (bool, error) { | |||||
| return p.eventBus.Offer(ctx, event) | |||||
| } | |||||
| @@ -0,0 +1,113 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "errors" | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| type Instruction interface { | |||||
| } | |||||
| type StateInstruction struct { | |||||
| stateName string | |||||
| stateMachineName string | |||||
| tenantId string | |||||
| end bool | |||||
| temporaryState statelang.State | |||||
| } | |||||
| func NewStateInstruction(stateMachineName string, tenantId string) *StateInstruction { | |||||
| return &StateInstruction{stateMachineName: stateMachineName, tenantId: tenantId} | |||||
| } | |||||
| func (s *StateInstruction) StateName() string { | |||||
| return s.stateName | |||||
| } | |||||
| func (s *StateInstruction) SetStateName(stateName string) { | |||||
| s.stateName = stateName | |||||
| } | |||||
| func (s *StateInstruction) StateMachineName() string { | |||||
| return s.stateMachineName | |||||
| } | |||||
| func (s *StateInstruction) SetStateMachineName(stateMachineName string) { | |||||
| s.stateMachineName = stateMachineName | |||||
| } | |||||
| func (s *StateInstruction) TenantId() string { | |||||
| return s.tenantId | |||||
| } | |||||
| func (s *StateInstruction) SetTenantId(tenantId string) { | |||||
| s.tenantId = tenantId | |||||
| } | |||||
| func (s *StateInstruction) End() bool { | |||||
| return s.end | |||||
| } | |||||
| func (s *StateInstruction) SetEnd(end bool) { | |||||
| s.end = end | |||||
| } | |||||
| func (s *StateInstruction) TemporaryState() statelang.State { | |||||
| return s.temporaryState | |||||
| } | |||||
| func (s *StateInstruction) SetTemporaryState(temporaryState statelang.State) { | |||||
| s.temporaryState = temporaryState | |||||
| } | |||||
| func (s *StateInstruction) GetState(context ProcessContext) (statelang.State, error) { | |||||
| if s.temporaryState != nil { | |||||
| return s.temporaryState, nil | |||||
| } | |||||
| if s.stateMachineName == "" { | |||||
| return nil, errors.New("stateMachineName is required") | |||||
| } | |||||
| stateMachineConfig, ok := context.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||||
| if !ok { | |||||
| return nil, errors.New("stateMachineConfig is required in context") | |||||
| } | |||||
| stateMachine, err := stateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(s.stateMachineName, s.tenantId) | |||||
| if err != nil { | |||||
| return nil, errors.New("get stateMachine in state machine repository error") | |||||
| } | |||||
| if stateMachine == nil { | |||||
| return nil, errors.New(fmt.Sprintf("stateMachine [%s] is not exist", s.stateMachineName)) | |||||
| } | |||||
| if s.stateName == "" { | |||||
| s.stateName = stateMachine.StartState() | |||||
| } | |||||
| state := stateMachine.States()[s.stateName] | |||||
| if state == nil { | |||||
| return nil, errors.New(fmt.Sprintf("state [%s] is not exist", s.stateName)) | |||||
| } | |||||
| return state, nil | |||||
| } | |||||
| @@ -0,0 +1,109 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "sync" | |||||
| ) | |||||
| type LoopContextHolder struct { | |||||
| nrOfInstances int32 | |||||
| nrOfActiveInstances int32 | |||||
| nrOfCompletedInstances int32 | |||||
| failEnd bool | |||||
| completionConditionSatisfied bool | |||||
| loopCounterStack []int | |||||
| forwardCounterStack []int | |||||
| collection interface{} | |||||
| } | |||||
| func NewLoopContextHolder() *LoopContextHolder { | |||||
| return &LoopContextHolder{ | |||||
| nrOfInstances: 0, | |||||
| nrOfActiveInstances: 0, | |||||
| nrOfCompletedInstances: 0, | |||||
| failEnd: false, | |||||
| completionConditionSatisfied: false, | |||||
| loopCounterStack: make([]int, 0), | |||||
| forwardCounterStack: make([]int, 0), | |||||
| collection: nil, | |||||
| } | |||||
| } | |||||
| func GetCurrentLoopContextHolder(ctx context.Context, processContext ProcessContext, forceCreate bool) *LoopContextHolder { | |||||
| mutex := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||||
| mutex.Lock() | |||||
| defer mutex.Unlock() | |||||
| loopContextHolder := processContext.GetVariable(constant.VarNameCurrentLoopContextHolder).(*LoopContextHolder) | |||||
| if loopContextHolder == nil && forceCreate { | |||||
| loopContextHolder = &LoopContextHolder{} | |||||
| processContext.SetVariable(constant.VarNameCurrentLoopContextHolder, loopContextHolder) | |||||
| } | |||||
| return loopContextHolder | |||||
| } | |||||
| func ClearCurrent(ctx context.Context, processContext ProcessContext) { | |||||
| processContext.RemoveVariable(constant.VarNameCurrentLoopContextHolder) | |||||
| } | |||||
| func (l *LoopContextHolder) NrOfInstances() int32 { | |||||
| return l.nrOfInstances | |||||
| } | |||||
| func (l *LoopContextHolder) NrOfActiveInstances() int32 { | |||||
| return l.nrOfActiveInstances | |||||
| } | |||||
| func (l *LoopContextHolder) NrOfCompletedInstances() int32 { | |||||
| return l.nrOfCompletedInstances | |||||
| } | |||||
| func (l *LoopContextHolder) FailEnd() bool { | |||||
| return l.failEnd | |||||
| } | |||||
| func (l *LoopContextHolder) SetFailEnd(failEnd bool) { | |||||
| l.failEnd = failEnd | |||||
| } | |||||
| func (l *LoopContextHolder) CompletionConditionSatisfied() bool { | |||||
| return l.completionConditionSatisfied | |||||
| } | |||||
| func (l *LoopContextHolder) SetCompletionConditionSatisfied(completionConditionSatisfied bool) { | |||||
| l.completionConditionSatisfied = completionConditionSatisfied | |||||
| } | |||||
| func (l *LoopContextHolder) LoopCounterStack() []int { | |||||
| return l.loopCounterStack | |||||
| } | |||||
| func (l *LoopContextHolder) ForwardCounterStack() []int { | |||||
| return l.forwardCounterStack | |||||
| } | |||||
| func (l *LoopContextHolder) Collection() interface{} { | |||||
| return l.collection | |||||
| } | |||||
| func (l *LoopContextHolder) SetCollection(collection interface{}) { | |||||
| l.collection = collection | |||||
| } | |||||
| @@ -0,0 +1,57 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| func GetLoopConfig(ctx context.Context, processContext ProcessContext, currentState statelang.State) state.Loop { | |||||
| if matchLoop(currentState) { | |||||
| taskState := currentState.(state.AbstractTaskState) | |||||
| stateMachineInstance := processContext.GetVariable(constant.VarNameStateMachineInst).(statelang.StateMachineInstance) | |||||
| stateMachineConfig := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||||
| if taskState.Loop() != nil { | |||||
| loop := taskState.Loop() | |||||
| collectionName := loop.Collection() | |||||
| if collectionName != "" { | |||||
| expression := CreateValueExpression(stateMachineConfig.ExpressionResolver(), collectionName) | |||||
| collection := GetValue(expression, stateMachineInstance.Context(), nil) | |||||
| collectionList := collection.([]any) | |||||
| if len(collectionList) > 0 { | |||||
| current := GetCurrentLoopContextHolder(ctx, processContext, true) | |||||
| current.SetCollection(collection) | |||||
| return loop | |||||
| } | |||||
| } | |||||
| log.Warn("State [{}] loop collection param [{}] invalid", currentState.Name(), collectionName) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func matchLoop(currentState statelang.State) bool { | |||||
| return currentState != nil && (constant.StateTypeServiceTask == currentState.Type() || | |||||
| constant.StateTypeScriptTask == currentState.Type() || constant.StateTypeSubStateMachine == currentState.Type()) | |||||
| } | |||||
| @@ -0,0 +1,149 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "strings" | |||||
| "sync" | |||||
| ) | |||||
| func CreateInputParams(processContext ProcessContext, expressionResolver expr.ExpressionResolver, | |||||
| stateInstance *statelang.StateInstanceImpl, serviceTaskState *state.AbstractTaskState, variablesFrom any) []any { | |||||
| inputAssignments := serviceTaskState.Input() | |||||
| if inputAssignments == nil || len(inputAssignments) == 0 { | |||||
| return inputAssignments | |||||
| } | |||||
| inputExpressions := serviceTaskState.InputExpressions() | |||||
| if inputExpressions == nil || len(inputExpressions) == 0 { | |||||
| lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||||
| lock.Lock() | |||||
| defer lock.Unlock() | |||||
| inputExpressions = serviceTaskState.InputExpressions() | |||||
| if inputExpressions == nil || len(inputExpressions) == 0 { | |||||
| inputExpressions = make([]any, 0, len(inputAssignments)) | |||||
| for _, assignment := range inputAssignments { | |||||
| inputExpressions = append(inputExpressions, CreateValueExpression(expressionResolver, assignment)) | |||||
| } | |||||
| } | |||||
| serviceTaskState.SetInputExpressions(inputExpressions) | |||||
| } | |||||
| inputValues := make([]any, 0, len(inputExpressions)) | |||||
| for _, valueExpression := range inputExpressions { | |||||
| value := GetValue(valueExpression, variablesFrom, stateInstance) | |||||
| inputValues = append(inputValues, value) | |||||
| } | |||||
| return inputValues | |||||
| } | |||||
| func CreateOutputParams(config StateMachineConfig, expressionResolver expr.ExpressionResolver, | |||||
| serviceTaskState *state.AbstractTaskState, variablesFrom any) (map[string]any, error) { | |||||
| outputAssignments := serviceTaskState.Output() | |||||
| if outputAssignments == nil || len(outputAssignments) == 0 { | |||||
| return make(map[string]any, 0), nil | |||||
| } | |||||
| outputExpressions := serviceTaskState.OutputExpressions() | |||||
| if outputExpressions == nil { | |||||
| config.ComponentLock().Lock() | |||||
| defer config.ComponentLock().Unlock() | |||||
| outputExpressions = serviceTaskState.OutputExpressions() | |||||
| if outputExpressions == nil { | |||||
| outputExpressions = make(map[string]any, len(outputAssignments)) | |||||
| for key, value := range outputAssignments { | |||||
| outputExpressions[key] = CreateValueExpression(expressionResolver, value) | |||||
| } | |||||
| } | |||||
| serviceTaskState.SetOutputExpressions(outputExpressions) | |||||
| } | |||||
| outputValues := make(map[string]any, len(outputExpressions)) | |||||
| for paramName, _ := range outputExpressions { | |||||
| outputValues[paramName] = GetValue(outputExpressions[paramName], variablesFrom, nil) | |||||
| } | |||||
| return outputValues, nil | |||||
| } | |||||
| func CreateValueExpression(expressionResolver expr.ExpressionResolver, paramAssignment any) any { | |||||
| var valueExpression any | |||||
| switch paramAssignment.(type) { | |||||
| case expr.Expression: | |||||
| valueExpression = paramAssignment | |||||
| case map[string]any: | |||||
| paramMapAssignment := paramAssignment.(map[string]any) | |||||
| paramMap := make(map[string]any, len(paramMapAssignment)) | |||||
| for key, value := range paramMapAssignment { | |||||
| paramMap[key] = CreateValueExpression(expressionResolver, value) | |||||
| } | |||||
| valueExpression = paramMap | |||||
| case []any: | |||||
| paramListAssignment := paramAssignment.([]any) | |||||
| paramList := make([]any, 0, len(paramListAssignment)) | |||||
| for _, value := range paramListAssignment { | |||||
| paramList = append(paramList, CreateValueExpression(expressionResolver, value)) | |||||
| } | |||||
| valueExpression = paramList | |||||
| case string: | |||||
| value := paramAssignment.(string) | |||||
| if !strings.HasPrefix(value, "$") { | |||||
| valueExpression = paramAssignment | |||||
| } | |||||
| valueExpression = expressionResolver.Expression(value) | |||||
| default: | |||||
| valueExpression = paramAssignment | |||||
| } | |||||
| return valueExpression | |||||
| } | |||||
| func GetValue(valueExpression any, variablesFrom any, stateInstance statelang.StateInstance) any { | |||||
| switch valueExpression.(type) { | |||||
| case expr.Expression: | |||||
| expression := valueExpression.(expr.Expression) | |||||
| value := expression.Value(variablesFrom) | |||||
| if _, ok := valueExpression.(expr.SequenceExpression); value != nil && stateInstance != nil && stateInstance.BusinessKey() == "" && ok { | |||||
| stateInstance.SetBusinessKey(fmt.Sprintf("%v", value)) | |||||
| } | |||||
| return value | |||||
| case map[string]any: | |||||
| mapValueExpression := valueExpression.(map[string]any) | |||||
| mapValue := make(map[string]any, len(mapValueExpression)) | |||||
| for key, value := range mapValueExpression { | |||||
| value = GetValue(value, variablesFrom, stateInstance) | |||||
| if value != nil { | |||||
| mapValue[key] = value | |||||
| } | |||||
| } | |||||
| return mapValue | |||||
| case []any: | |||||
| valueExpressionList := valueExpression.([]any) | |||||
| listValue := make([]any, 0, len(valueExpression.([]any))) | |||||
| for i, _ := range valueExpressionList { | |||||
| listValue = append(listValue, GetValue(valueExpressionList[i], variablesFrom, stateInstance)) | |||||
| } | |||||
| return listValue | |||||
| default: | |||||
| return valueExpression | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,225 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "sync" | |||||
| ) | |||||
| type ProcessContext interface { | |||||
| GetVariable(name string) interface{} | |||||
| SetVariable(name string, value interface{}) | |||||
| GetVariables() map[string]interface{} | |||||
| SetVariables(variables map[string]interface{}) | |||||
| RemoveVariable(name string) interface{} | |||||
| HasVariable(name string) bool | |||||
| GetInstruction() Instruction | |||||
| SetInstruction(instruction Instruction) | |||||
| } | |||||
| type HierarchicalProcessContext interface { | |||||
| ProcessContext | |||||
| GetVariableLocally(name string) interface{} | |||||
| SetVariableLocally(name string, value interface{}) | |||||
| GetVariablesLocally() map[string]interface{} | |||||
| SetVariablesLocally(variables map[string]interface{}) | |||||
| RemoveVariableLocally(name string) interface{} | |||||
| HasVariableLocally(name string) bool | |||||
| ClearLocally() | |||||
| } | |||||
| type ProcessContextImpl struct { | |||||
| parent ProcessContext | |||||
| mu sync.RWMutex | |||||
| mp map[string]interface{} | |||||
| instruction Instruction | |||||
| } | |||||
| func NewProcessContextImpl() *ProcessContextImpl { | |||||
| return &ProcessContextImpl{ | |||||
| mp: make(map[string]interface{}), | |||||
| } | |||||
| } | |||||
| func (p *ProcessContextImpl) GetVariable(name string) interface{} { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| value, ok := p.mp[name] | |||||
| if ok { | |||||
| return value | |||||
| } | |||||
| if p.parent != nil { | |||||
| return p.parent.GetVariable(name) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (p *ProcessContextImpl) SetVariable(name string, value interface{}) { | |||||
| p.mu.Lock() | |||||
| defer p.mu.Unlock() | |||||
| _, ok := p.mp[name] | |||||
| if ok { | |||||
| p.mp[name] = value | |||||
| } else { | |||||
| if p.parent != nil { | |||||
| p.parent.SetVariable(name, value) | |||||
| } else { | |||||
| p.mp[name] = value | |||||
| } | |||||
| } | |||||
| } | |||||
| func (p *ProcessContextImpl) GetVariables() map[string]interface{} { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| newVariablesMap := make(map[string]interface{}) | |||||
| if p.parent != nil { | |||||
| variables := p.parent.GetVariables() | |||||
| for k, v := range variables { | |||||
| newVariablesMap[k] = v | |||||
| } | |||||
| } | |||||
| for k, v := range p.mp { | |||||
| newVariablesMap[k] = v | |||||
| } | |||||
| return newVariablesMap | |||||
| } | |||||
| func (p *ProcessContextImpl) SetVariables(variables map[string]interface{}) { | |||||
| for k, v := range variables { | |||||
| p.SetVariable(k, v) | |||||
| } | |||||
| } | |||||
| func (p *ProcessContextImpl) RemoveVariable(name string) interface{} { | |||||
| p.mu.Lock() | |||||
| defer p.mu.Unlock() | |||||
| value, ok := p.mp[name] | |||||
| if ok { | |||||
| delete(p.mp, name) | |||||
| return value | |||||
| } | |||||
| if p.parent != nil { | |||||
| return p.parent.RemoveVariable(name) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (p *ProcessContextImpl) HasVariable(name string) bool { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| _, ok := p.mp[name] | |||||
| if ok { | |||||
| return true | |||||
| } | |||||
| if p.parent != nil { | |||||
| return p.parent.HasVariable(name) | |||||
| } | |||||
| return false | |||||
| } | |||||
| func (p *ProcessContextImpl) GetInstruction() Instruction { | |||||
| return p.instruction | |||||
| } | |||||
| func (p *ProcessContextImpl) SetInstruction(instruction Instruction) { | |||||
| p.instruction = instruction | |||||
| } | |||||
| func (p *ProcessContextImpl) GetVariableLocally(name string) interface{} { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| value, _ := p.mp[name] | |||||
| return value | |||||
| } | |||||
| func (p *ProcessContextImpl) SetVariableLocally(name string, value interface{}) { | |||||
| p.mu.Lock() | |||||
| defer p.mu.Unlock() | |||||
| p.mp[name] = value | |||||
| } | |||||
| func (p *ProcessContextImpl) GetVariablesLocally() map[string]interface{} { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| newVariablesMap := make(map[string]interface{}, len(p.mp)) | |||||
| for k, v := range p.mp { | |||||
| newVariablesMap[k] = v | |||||
| } | |||||
| return newVariablesMap | |||||
| } | |||||
| func (p *ProcessContextImpl) SetVariablesLocally(variables map[string]interface{}) { | |||||
| for k, v := range variables { | |||||
| p.SetVariableLocally(k, v) | |||||
| } | |||||
| } | |||||
| func (p *ProcessContextImpl) RemoveVariableLocally(name string) interface{} { | |||||
| p.mu.Lock() | |||||
| defer p.mu.Unlock() | |||||
| value, _ := p.mp[name] | |||||
| delete(p.mp, name) | |||||
| return value | |||||
| } | |||||
| func (p *ProcessContextImpl) HasVariableLocally(name string) bool { | |||||
| p.mu.RLock() | |||||
| defer p.mu.RUnlock() | |||||
| _, ok := p.mp[name] | |||||
| return ok | |||||
| } | |||||
| func (p *ProcessContextImpl) ClearLocally() { | |||||
| p.mu.Lock() | |||||
| defer p.mu.Unlock() | |||||
| p.mp = map[string]interface{}{} | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| ) | |||||
| type ProcessController interface { | |||||
| Process(ctx context.Context, context ProcessContext) error | |||||
| } | |||||
| type ProcessControllerImpl struct { | |||||
| businessProcessor BusinessProcessor | |||||
| } | |||||
| func (p *ProcessControllerImpl) Process(ctx context.Context, context ProcessContext) error { | |||||
| if err := p.businessProcessor.Process(ctx, context); err != nil { | |||||
| return err | |||||
| } | |||||
| if err := p.businessProcessor.Route(ctx, context); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (p *ProcessControllerImpl) BusinessProcessor() BusinessProcessor { | |||||
| return p.businessProcessor | |||||
| } | |||||
| func (p *ProcessControllerImpl) SetBusinessProcessor(businessProcessor BusinessProcessor) { | |||||
| p.businessProcessor = businessProcessor | |||||
| } | |||||
| @@ -0,0 +1,717 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| "time" | |||||
| ) | |||||
| type ProcessCtrlStateMachineEngine struct { | |||||
| StateMachineConfig StateMachineConfig | |||||
| } | |||||
| func NewProcessCtrlStateMachineEngine() *ProcessCtrlStateMachineEngine { | |||||
| return &ProcessCtrlStateMachineEngine{ | |||||
| StateMachineConfig: NewDefaultStateMachineConfig(), | |||||
| } | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) Start(ctx context.Context, stateMachineName string, tenantId string, | |||||
| startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||||
| return p.startInternal(ctx, stateMachineName, tenantId, "", startParams, false, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) StartAsync(ctx context.Context, stateMachineName string, tenantId string, | |||||
| startParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| return p.startInternal(ctx, stateMachineName, tenantId, "", startParams, true, callback) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) StartWithBusinessKey(ctx context.Context, stateMachineName string, | |||||
| tenantId string, businessKey string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||||
| return p.startInternal(ctx, stateMachineName, tenantId, businessKey, startParams, false, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) StartWithBusinessKeyAsync(ctx context.Context, stateMachineName string, | |||||
| tenantId string, businessKey string, startParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| return p.startInternal(ctx, stateMachineName, tenantId, businessKey, startParams, true, callback) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) Forward(ctx context.Context, stateMachineInstId string, | |||||
| replaceParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||||
| return p.forwardInternal(ctx, stateMachineInstId, replaceParams, false, false, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) ForwardAsync(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| return p.forwardInternal(ctx, stateMachineInstId, replaceParams, false, true, callback) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) Compensate(ctx context.Context, stateMachineInstId string, | |||||
| replaceParams map[string]any) (statelang.StateMachineInstance, error) { | |||||
| return p.compensateInternal(ctx, stateMachineInstId, replaceParams, false, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) CompensateAsync(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| return p.compensateInternal(ctx, stateMachineInstId, replaceParams, true, callback) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) SkipAndForward(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||||
| return p.forwardInternal(ctx, stateMachineInstId, replaceParams, true, false, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) SkipAndForwardAsync(ctx context.Context, stateMachineInstId string, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| return p.forwardInternal(ctx, stateMachineInstId, nil, true, true, callback) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) GetStateMachineConfig() StateMachineConfig { | |||||
| return p.StateMachineConfig | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) ReloadStateMachineInstance(ctx context.Context, instId string) (statelang.StateMachineInstance, error) { | |||||
| inst, err := p.StateMachineConfig.StateLogStore().GetStateMachineInstance(instId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if inst != nil { | |||||
| stateMachine := inst.StateMachine() | |||||
| if stateMachine == nil { | |||||
| stateMachine, err = p.StateMachineConfig.StateMachineRepository().GetStateMachineById(inst.MachineID()) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| inst.SetStateMachine(stateMachine) | |||||
| } | |||||
| if stateMachine == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "StateMachine[id:"+inst.MachineID()+"] not exist.", nil) | |||||
| } | |||||
| stateList := inst.StateList() | |||||
| if len(stateList) == 0 { | |||||
| stateList, err = p.StateMachineConfig.StateLogStore().GetStateInstanceListByMachineInstanceId(instId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if len(stateList) > 0 { | |||||
| for _, tmpStateInstance := range stateList { | |||||
| inst.PutState(tmpStateInstance.ID(), tmpStateInstance) | |||||
| } | |||||
| } | |||||
| } | |||||
| if len(inst.EndParams()) == 0 { | |||||
| endParams, err := p.replayContextVariables(ctx, inst) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| inst.SetEndParams(endParams) | |||||
| } | |||||
| } | |||||
| return inst, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) startInternal(ctx context.Context, stateMachineName string, tenantId string, | |||||
| businessKey string, startParams map[string]interface{}, async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| if tenantId == "" { | |||||
| tenantId = p.StateMachineConfig.DefaultTenantId() | |||||
| } | |||||
| stateMachineInstance, err := p.createMachineInstance(stateMachineName, tenantId, businessKey, startParams) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| // Build the process_ctrl context. | |||||
| processContextBuilder := NewProcessContextBuilder(). | |||||
| WithProcessType(process.StateLang). | |||||
| WithOperationName(constant.OperationNameStart). | |||||
| WithAsyncCallback(callback). | |||||
| WithInstruction(NewStateInstruction(stateMachineName, tenantId)). | |||||
| WithStateMachineInstance(stateMachineInstance). | |||||
| WithStateMachineConfig(p.StateMachineConfig). | |||||
| WithStateMachineEngine(p). | |||||
| WithIsAsyncExecution(async) | |||||
| contextMap := p.copyMap(startParams) | |||||
| stateMachineInstance.SetContext(contextMap) | |||||
| processContext := processContextBuilder.WithStateMachineContextVariables(contextMap).Build() | |||||
| if stateMachineInstance.StateMachine().IsPersist() && p.StateMachineConfig.StateLogStore() != nil { | |||||
| err := p.StateMachineConfig.StateLogStore().RecordStateMachineStarted(ctx, stateMachineInstance, processContext) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| if stateMachineInstance.ID() == "" { | |||||
| stateMachineInstance.SetID(p.StateMachineConfig.SeqGenerator().GenerateId(constant.SeqEntityStateMachineInst, "")) | |||||
| } | |||||
| var eventPublisher EventPublisher | |||||
| if async { | |||||
| eventPublisher = p.StateMachineConfig.AsyncEventPublisher() | |||||
| } else { | |||||
| eventPublisher = p.StateMachineConfig.EventPublisher() | |||||
| } | |||||
| _, err = eventPublisher.PushEvent(ctx, processContext) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) forwardInternal(ctx context.Context, stateMachineInstId string, | |||||
| replaceParams map[string]interface{}, skip bool, async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| stateMachineInstance, err := p.reloadStateMachineInstance(ctx, stateMachineInstId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if stateMachineInstance == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.StateMachineInstanceNotExists, "StateMachineInstance is not exists", nil) | |||||
| } | |||||
| if stateMachineInstance.Status() == statelang.SU && stateMachineInstance.CompensationStatus() == "" { | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| acceptStatus := []statelang.ExecutionStatus{statelang.FA, statelang.UN, statelang.RU} | |||||
| if _, err := p.checkStatus(ctx, stateMachineInstance, acceptStatus, nil, stateMachineInstance.Status(), "", "forward"); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| actList := stateMachineInstance.StateList() | |||||
| if len(actList) == 0 { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| fmt.Sprintf("StateMachineInstance[id:%s] has no stateInstance, please start a new StateMachine execution instead", stateMachineInstId), nil) | |||||
| } | |||||
| lastForwardState, err := p.findOutLastForwardStateInstance(actList) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if lastForwardState == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| fmt.Sprintf("StateMachineInstance[id:%s] Cannot find last forward execution stateInstance", stateMachineInstId), nil) | |||||
| } | |||||
| contextBuilder := NewProcessContextBuilder(). | |||||
| WithProcessType(process.StateLang). | |||||
| WithOperationName(constant.OperationNameForward). | |||||
| WithAsyncCallback(callback). | |||||
| WithStateMachineInstance(stateMachineInstance). | |||||
| WithStateInstance(lastForwardState). | |||||
| WithStateMachineConfig(p.StateMachineConfig). | |||||
| WithStateMachineEngine(p). | |||||
| WithIsAsyncExecution(async) | |||||
| context := contextBuilder.Build() | |||||
| contextVariables, err := p.getStateMachineContextVariables(ctx, stateMachineInstance) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if replaceParams != nil { | |||||
| for k, v := range replaceParams { | |||||
| contextVariables[k] = v | |||||
| } | |||||
| } | |||||
| p.putBusinesskeyToContextariables(stateMachineInstance, contextVariables) | |||||
| concurrentContextVariables := p.copyMap(contextVariables) | |||||
| context.SetVariable(constant.VarNameStateMachineContext, concurrentContextVariables) | |||||
| stateMachineInstance.SetContext(concurrentContextVariables) | |||||
| originStateName := GetOriginStateName(lastForwardState) | |||||
| lastState := stateMachineInstance.StateMachine().State(originStateName) | |||||
| loop := GetLoopConfig(ctx, context, lastState) | |||||
| if loop != nil && lastForwardState.Status() == statelang.SU { | |||||
| lastForwardState = p.findOutLastNeedForwardStateInstance(ctx, context) | |||||
| } | |||||
| context.SetVariable(lastForwardState.Name()+constant.VarNameRetriedStateInstId, lastForwardState.ID()) | |||||
| if lastForwardState.Type() == constant.StateTypeSubStateMachine && lastForwardState.CompensationStatus() != statelang.SU { | |||||
| context.SetVariable(constant.VarNameIsForSubStatMachineForward, true) | |||||
| } | |||||
| if lastForwardState.Status() != statelang.SU { | |||||
| lastForwardState.SetIgnoreStatus(true) | |||||
| } | |||||
| inst := NewStateInstruction(stateMachineInstance.StateMachine().Name(), stateMachineInstance.TenantID()) | |||||
| if skip || lastForwardState.Status() == statelang.SU { | |||||
| next := "" | |||||
| curState := stateMachineInstance.StateMachine().State(GetOriginStateName(lastForwardState)) | |||||
| if taskState, ok := curState.(*state.AbstractTaskState); ok { | |||||
| next = taskState.Next() | |||||
| } | |||||
| if next == "" { | |||||
| log.Warn(fmt.Sprintf("Last Forward execution StateInstance was succeed, and it has not Next State, skip forward operation")) | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| inst.SetStateName(next) | |||||
| } else { | |||||
| if lastForwardState.Status() == statelang.RU && !IsTimeout(lastForwardState.StartedTime(), p.StateMachineConfig.ServiceInvokeTimeout()) { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| fmt.Sprintf("State [%s] is running, operation[forward] denied", lastForwardState.Name()), nil) | |||||
| } | |||||
| inst.SetStateName(GetOriginStateName(lastForwardState)) | |||||
| } | |||||
| context.SetInstruction(inst) | |||||
| stateMachineInstance.SetStatus(statelang.RU) | |||||
| stateMachineInstance.SetRunning(true) | |||||
| log.Info(fmt.Sprintf("Operation [forward] started stateMachineInstance[id:%s]", stateMachineInstance.ID())) | |||||
| if stateMachineInstance.StateMachine().IsPersist() { | |||||
| if err := p.StateMachineConfig.StateLogStore().RecordStateMachineRestarted(ctx, stateMachineInstance, context); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| curState, err := inst.GetState(context) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| loop = GetLoopConfig(ctx, context, curState) | |||||
| if loop != nil { | |||||
| inst.SetTemporaryState(state.NewLoopStartStateImpl()) | |||||
| } | |||||
| if async { | |||||
| if _, err := p.StateMachineConfig.AsyncEventPublisher().PushEvent(ctx, context); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } else { | |||||
| if _, err := p.StateMachineConfig.EventPublisher().PushEvent(ctx, context); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) findOutLastForwardStateInstance(stateInstanceList []statelang.StateInstance) (statelang.StateInstance, error) { | |||||
| var lastForwardStateInstance statelang.StateInstance | |||||
| var err error | |||||
| for i := len(stateInstanceList) - 1; i >= 0; i-- { | |||||
| stateInstance := stateInstanceList[i] | |||||
| if !stateInstance.IsForCompensation() { | |||||
| if stateInstance.CompensationStatus() == statelang.SU { | |||||
| continue | |||||
| } | |||||
| if stateInstance.Type() == constant.StateTypeSubStateMachine { | |||||
| finalState := stateInstance | |||||
| for finalState.StateIDRetriedFor() != "" { | |||||
| if finalState, err = p.StateMachineConfig.StateLogStore().GetStateInstance(finalState.StateIDRetriedFor(), | |||||
| finalState.MachineInstanceID()); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| subInst, _ := p.StateMachineConfig.StateLogStore().GetStateMachineInstanceByParentId(GenerateParentId(finalState)) | |||||
| if len(subInst) > 0 { | |||||
| if subInst[0].CompensationStatus() == statelang.SU { | |||||
| continue | |||||
| } | |||||
| if subInst[0].CompensationStatus() == statelang.UN { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ForwardInvalid, | |||||
| "Last forward execution state instance is SubStateMachine and compensation status is [UN], Operation[forward] denied, stateInstanceId:"+stateInstance.ID(), | |||||
| nil) | |||||
| } | |||||
| } | |||||
| } else if stateInstance.CompensationStatus() == statelang.UN { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ForwardInvalid, | |||||
| "Last forward execution state instance compensation status is [UN], Operation[forward] denied, stateInstanceId:"+stateInstance.ID(), | |||||
| nil) | |||||
| } | |||||
| lastForwardStateInstance = stateInstance | |||||
| break | |||||
| } | |||||
| } | |||||
| return lastForwardStateInstance, nil | |||||
| } | |||||
| // copyMap not deep copy, so best practice: Don’t pass by reference | |||||
| func (p ProcessCtrlStateMachineEngine) copyMap(startParams map[string]interface{}) map[string]interface{} { | |||||
| copyMap := make(map[string]interface{}, len(startParams)) | |||||
| for k, v := range startParams { | |||||
| copyMap[k] = v | |||||
| } | |||||
| return copyMap | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) createMachineInstance(stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||||
| stateMachine, err := p.StateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(stateMachineName, tenantId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if stateMachine == nil { | |||||
| return nil, errors.New("StateMachine [" + stateMachineName + "] is not exists") | |||||
| } | |||||
| stateMachineInstance := statelang.NewStateMachineInstanceImpl() | |||||
| stateMachineInstance.SetStateMachine(stateMachine) | |||||
| stateMachineInstance.SetTenantID(tenantId) | |||||
| stateMachineInstance.SetBusinessKey(businessKey) | |||||
| stateMachineInstance.SetStartParams(startParams) | |||||
| if startParams != nil { | |||||
| if businessKey != "" { | |||||
| startParams[constant.VarNameBusinesskey] = businessKey | |||||
| } | |||||
| if startParams[constant.VarNameParentId] != nil { | |||||
| parentId, ok := startParams[constant.VarNameParentId].(string) | |||||
| if !ok { | |||||
| } | |||||
| stateMachineInstance.SetParentID(parentId) | |||||
| delete(startParams, constant.VarNameParentId) | |||||
| } | |||||
| } | |||||
| stateMachineInstance.SetStatus(statelang.RU) | |||||
| stateMachineInstance.SetRunning(true) | |||||
| now := time.Now() | |||||
| stateMachineInstance.SetStartedTime(now) | |||||
| stateMachineInstance.SetUpdatedTime(now) | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) compensateInternal(ctx context.Context, stateMachineInstId string, replaceParams map[string]any, | |||||
| async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||||
| stateMachineInstance, err := p.reloadStateMachineInstance(ctx, stateMachineInstId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if stateMachineInstance == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.StateMachineInstanceNotExists, | |||||
| "StateMachineInstance is not exits", nil) | |||||
| } | |||||
| if statelang.SU == stateMachineInstance.CompensationStatus() { | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| if stateMachineInstance.CompensationStatus() != "" { | |||||
| denyStatus := make([]statelang.ExecutionStatus, 0) | |||||
| denyStatus = append(denyStatus, statelang.SU) | |||||
| p.checkStatus(ctx, stateMachineInstance, nil, denyStatus, "", stateMachineInstance.CompensationStatus(), | |||||
| "compensate") | |||||
| } | |||||
| if replaceParams != nil { | |||||
| for key, value := range replaceParams { | |||||
| stateMachineInstance.EndParams()[key] = value | |||||
| } | |||||
| } | |||||
| contextBuilder := NewProcessContextBuilder().WithProcessType(process.StateLang). | |||||
| WithOperationName(constant.OperationNameCompensate).WithAsyncCallback(callback). | |||||
| WithStateMachineInstance(stateMachineInstance). | |||||
| WithStateMachineConfig(p.StateMachineConfig).WithStateMachineEngine(p).WithIsAsyncExecution(async) | |||||
| context := contextBuilder.Build() | |||||
| contextVariables, err := p.getStateMachineContextVariables(ctx, stateMachineInstance) | |||||
| if replaceParams != nil { | |||||
| for key, value := range replaceParams { | |||||
| contextVariables[key] = value | |||||
| } | |||||
| } | |||||
| p.putBusinesskeyToContextariables(stateMachineInstance, contextVariables) | |||||
| // TODO: Here is not use sync.map, make sure whether to use it | |||||
| concurrentContextVariables := make(map[string]any) | |||||
| p.nullSafeCopy(contextVariables, concurrentContextVariables) | |||||
| context.SetVariable(constant.VarNameStateMachineContext, concurrentContextVariables) | |||||
| stateMachineInstance.SetContext(concurrentContextVariables) | |||||
| tempCompensationTriggerState := state.NewCompensationTriggerStateImpl() | |||||
| tempCompensationTriggerState.SetStateMachine(stateMachineInstance.StateMachine()) | |||||
| stateMachineInstance.SetRunning(true) | |||||
| log.Info("Operation [compensate] start. stateMachineInstance[id:" + stateMachineInstance.ID() + "]") | |||||
| if stateMachineInstance.StateMachine().IsPersist() { | |||||
| err := p.StateMachineConfig.StateLogStore().RecordStateMachineRestarted(ctx, stateMachineInstance, context) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| inst := NewStateInstruction(stateMachineInstance.TenantID(), stateMachineInstance.StateMachine().Name()) | |||||
| inst.SetTemporaryState(tempCompensationTriggerState) | |||||
| context.SetInstruction(inst) | |||||
| if async { | |||||
| _, err := p.StateMachineConfig.AsyncEventPublisher().PushEvent(ctx, context) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } else { | |||||
| _, err := p.StateMachineConfig.EventPublisher().PushEvent(ctx, context) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| return stateMachineInstance, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) reloadStateMachineInstance(ctx context.Context, instId string) (statelang.StateMachineInstance, error) { | |||||
| instance, err := p.StateMachineConfig.StateLogStore().GetStateMachineInstance(instId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if instance != nil { | |||||
| stateMachine := instance.StateMachine() | |||||
| if stateMachine == nil { | |||||
| stateMachine, err = p.StateMachineConfig.StateMachineRepository().GetStateMachineById(instance.MachineID()) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| instance.SetStateMachine(stateMachine) | |||||
| } | |||||
| if stateMachine == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "StateMachine[id:"+instance.MachineID()+"] not exist.", nil) | |||||
| } | |||||
| stateList := instance.StateList() | |||||
| if stateList == nil || len(stateList) == 0 { | |||||
| stateList, err = p.StateMachineConfig.StateLogStore().GetStateInstanceListByMachineInstanceId(instId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if stateList != nil && len(stateList) > 0 { | |||||
| for _, tmpStateInstance := range stateList { | |||||
| instance.PutState(tmpStateInstance.ID(), tmpStateInstance) | |||||
| } | |||||
| } | |||||
| } | |||||
| if instance.EndParams() == nil || len(instance.EndParams()) == 0 { | |||||
| variables, err := p.replayContextVariables(ctx, instance) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| instance.SetEndParams(variables) | |||||
| } | |||||
| } | |||||
| return instance, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) replayContextVariables(ctx context.Context, stateMachineInstance statelang.StateMachineInstance) (map[string]any, error) { | |||||
| contextVariables := make(map[string]any) | |||||
| if stateMachineInstance.StartParams() != nil { | |||||
| for key, value := range stateMachineInstance.StartParams() { | |||||
| contextVariables[key] = value | |||||
| } | |||||
| } | |||||
| stateInstanceList := stateMachineInstance.StateList() | |||||
| if stateInstanceList == nil || len(stateInstanceList) == 0 { | |||||
| return contextVariables, nil | |||||
| } | |||||
| for _, stateInstance := range stateInstanceList { | |||||
| serviceOutputParams := stateInstance.OutputParams() | |||||
| if serviceOutputParams != nil { | |||||
| serviceTaskStateImpl, ok := stateMachineInstance.StateMachine().State(GetOriginStateName(stateInstance)).(*state.ServiceTaskStateImpl) | |||||
| if !ok { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "Cannot find State by state name ["+stateInstance.Name()+"], may be this is a bug", nil) | |||||
| } | |||||
| if serviceTaskStateImpl.Output() != nil && len(serviceTaskStateImpl.Output()) != 0 { | |||||
| outputVariablesToContext, err := CreateOutputParams(p.StateMachineConfig, | |||||
| p.StateMachineConfig.ExpressionResolver(), serviceTaskStateImpl.AbstractTaskState, serviceOutputParams) | |||||
| if err != nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "Context variable replay failed", err) | |||||
| } | |||||
| if outputVariablesToContext != nil && len(outputVariablesToContext) != 0 { | |||||
| for key, value := range outputVariablesToContext { | |||||
| contextVariables[key] = value | |||||
| } | |||||
| } | |||||
| if len(stateInstance.BusinessKey()) > 0 { | |||||
| contextVariables[serviceTaskStateImpl.Name()+constant.VarNameBusinesskey] = stateInstance.BusinessKey() | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return contextVariables, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) checkStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, | |||||
| acceptStatus []statelang.ExecutionStatus, denyStatus []statelang.ExecutionStatus, status statelang.ExecutionStatus, | |||||
| compenStatus statelang.ExecutionStatus, operation string) (bool, error) { | |||||
| if status != "" && compenStatus != "" { | |||||
| return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||||
| "status and compensationStatus are not supported at the same time", nil) | |||||
| } | |||||
| if status == "" && compenStatus == "" { | |||||
| return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||||
| "status and compensationStatus must input at least one", nil) | |||||
| } | |||||
| if statelang.SU == compenStatus { | |||||
| message := p.buildExceptionMessage(stateMachineInstance, nil, nil, "", statelang.SU, operation) | |||||
| return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| message, nil) | |||||
| } | |||||
| if stateMachineInstance.IsRunning() && | |||||
| !IsTimeout(stateMachineInstance.UpdatedTime(), p.StateMachineConfig.TransOperationTimeout()) { | |||||
| return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| "StateMachineInstance [id:"+stateMachineInstance.ID()+"] is running, operation["+operation+ | |||||
| "] denied", nil) | |||||
| } | |||||
| if (denyStatus == nil || len(denyStatus) == 0) && (acceptStatus == nil || len(acceptStatus) == 0) { | |||||
| return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||||
| "StateMachineInstance[id:"+stateMachineInstance.ID()+ | |||||
| "], acceptable status and deny status must input at least one", nil) | |||||
| } | |||||
| currentStatus := compenStatus | |||||
| if status != "" { | |||||
| currentStatus = status | |||||
| } | |||||
| if denyStatus != nil && len(denyStatus) == 0 { | |||||
| for _, tempDenyStatus := range denyStatus { | |||||
| if tempDenyStatus == currentStatus { | |||||
| message := p.buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, | |||||
| compenStatus, operation) | |||||
| return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| message, nil) | |||||
| } | |||||
| } | |||||
| } | |||||
| if acceptStatus == nil || len(acceptStatus) == 0 { | |||||
| return true, nil | |||||
| } else { | |||||
| for _, tempStatus := range acceptStatus { | |||||
| if tempStatus == currentStatus { | |||||
| return true, nil | |||||
| } | |||||
| } | |||||
| } | |||||
| message := p.buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, compenStatus, | |||||
| operation) | |||||
| return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||||
| message, nil) | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) getStateMachineContextVariables(ctx context.Context, | |||||
| stateMachineInstance statelang.StateMachineInstance) (map[string]any, error) { | |||||
| contextVariables := stateMachineInstance.EndParams() | |||||
| if contextVariables == nil || len(contextVariables) == 0 { | |||||
| return p.replayContextVariables(ctx, stateMachineInstance) | |||||
| } | |||||
| return contextVariables, nil | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) buildExceptionMessage(instance statelang.StateMachineInstance, | |||||
| acceptStatus []statelang.ExecutionStatus, denyStatus []statelang.ExecutionStatus, status statelang.ExecutionStatus, | |||||
| compenStatus statelang.ExecutionStatus, operation string) string { | |||||
| message := fmt.Sprintf("StateMachineInstance[id:%s]", instance.ID()) | |||||
| if len(acceptStatus) > 0 { | |||||
| message += ",acceptable status :" | |||||
| for _, tempStatus := range acceptStatus { | |||||
| message += string(tempStatus) + " " | |||||
| } | |||||
| } | |||||
| if len(denyStatus) > 0 { | |||||
| message += ",deny status:" | |||||
| for _, tempStatus := range denyStatus { | |||||
| message += string(tempStatus) + " " | |||||
| } | |||||
| } | |||||
| if status != "" { | |||||
| message += ",current status:" + string(status) | |||||
| } | |||||
| if compenStatus != "" { | |||||
| message += ",current compensation status:" + string(compenStatus) | |||||
| } | |||||
| message += fmt.Sprintf(",so operation [%s] denied", operation) | |||||
| return message | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) putBusinesskeyToContextariables(instance statelang.StateMachineInstance, variables map[string]any) { | |||||
| if instance.BusinessKey() != "" && variables[constant.VarNameBusinesskey] == "" { | |||||
| variables[constant.VarNameBusinesskey] = instance.BusinessKey() | |||||
| } | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) nullSafeCopy(srcMap map[string]any, destMap map[string]any) { | |||||
| for key, value := range srcMap { | |||||
| if value == nil { | |||||
| destMap[key] = value | |||||
| } | |||||
| } | |||||
| } | |||||
| func (p ProcessCtrlStateMachineEngine) findOutLastNeedForwardStateInstance(ctx context.Context, processContext ProcessContext) statelang.StateInstance { | |||||
| stateMachineInstance := processContext.GetVariable(constant.VarNameStateMachineInst).(statelang.StateMachineInstance) | |||||
| lastForwardState := processContext.GetVariable(constant.VarNameStateInst).(statelang.StateInstance) | |||||
| actList := stateMachineInstance.StateList() | |||||
| for i := len(actList) - 1; i >= 0; i-- { | |||||
| stateInstance := actList[i] | |||||
| if GetOriginStateName(stateInstance) == GetOriginStateName(lastForwardState) && stateInstance.Status() != statelang.SU { | |||||
| return stateInstance | |||||
| } | |||||
| } | |||||
| return lastForwardState | |||||
| } | |||||
| @@ -0,0 +1,211 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type RouterHandler interface { | |||||
| Route(ctx context.Context, processContext ProcessContext) error | |||||
| } | |||||
| type ProcessRouter interface { | |||||
| Route(ctx context.Context, processContext ProcessContext) error | |||||
| } | |||||
| type InterceptAbleStateRouter interface { | |||||
| StateRouter | |||||
| StateRouterInterceptor() []StateRouterInterceptor | |||||
| RegistryStateRouterInterceptor(stateRouterInterceptor StateRouterInterceptor) | |||||
| } | |||||
| type StateRouter interface { | |||||
| Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) | |||||
| } | |||||
| type StateRouterInterceptor interface { | |||||
| PreRoute(ctx context.Context, processContext ProcessContext, state statelang.State) error | |||||
| PostRoute(ctx context.Context, processContext ProcessContext, instruction Instruction, err error) error | |||||
| Match(stateType string) bool | |||||
| } | |||||
| type DefaultRouterHandler struct { | |||||
| eventPublisher EventPublisher | |||||
| processRouters map[string]ProcessRouter | |||||
| } | |||||
| func (d *DefaultRouterHandler) Route(ctx context.Context, processContext ProcessContext) error { | |||||
| processType := d.matchProcessType(ctx, processContext) | |||||
| if processType == "" { | |||||
| log.Warnf("Process type not found, context= %s", processContext) | |||||
| return errors.New("Process type not found") | |||||
| } | |||||
| processRouter := d.processRouters[string(processType)] | |||||
| if processRouter == nil { | |||||
| log.Errorf("Cannot find process router by type %s, context = %s", processType, processContext) | |||||
| return errors.New("Process router not found") | |||||
| } | |||||
| instruction := processRouter.Route(ctx, processContext) | |||||
| if instruction == nil { | |||||
| log.Info("route instruction is null, process end") | |||||
| } else { | |||||
| processContext.SetInstruction(instruction) | |||||
| _, err := d.eventPublisher.PushEvent(ctx, processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (d *DefaultRouterHandler) matchProcessType(ctx context.Context, processContext ProcessContext) process.ProcessType { | |||||
| processType, ok := processContext.GetVariable(constant.VarNameProcessType).(process.ProcessType) | |||||
| if !ok || processType == "" { | |||||
| processType = process.StateLang | |||||
| } | |||||
| return processType | |||||
| } | |||||
| func (d *DefaultRouterHandler) EventPublisher() EventPublisher { | |||||
| return d.eventPublisher | |||||
| } | |||||
| func (d *DefaultRouterHandler) SetEventPublisher(eventPublisher EventPublisher) { | |||||
| d.eventPublisher = eventPublisher | |||||
| } | |||||
| func (d *DefaultRouterHandler) ProcessRouters() map[string]ProcessRouter { | |||||
| return d.processRouters | |||||
| } | |||||
| func (d *DefaultRouterHandler) SetProcessRouters(processRouters map[string]ProcessRouter) { | |||||
| d.processRouters = processRouters | |||||
| } | |||||
| type StateMachineProcessRouter struct { | |||||
| stateRouters map[string]StateRouter | |||||
| } | |||||
| func (s *StateMachineProcessRouter) Route(ctx context.Context, processContext ProcessContext) (Instruction, error) { | |||||
| stateInstruction, ok := processContext.GetInstruction().(StateInstruction) | |||||
| if !ok { | |||||
| return nil, errors.New("instruction is not a state instruction") | |||||
| } | |||||
| var state statelang.State | |||||
| if stateInstruction.TemporaryState() != nil { | |||||
| state = stateInstruction.TemporaryState() | |||||
| stateInstruction.SetTemporaryState(nil) | |||||
| } else { | |||||
| stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||||
| if !ok { | |||||
| return nil, errors.New("state machine config not found") | |||||
| } | |||||
| stateMachine, err := stateMachineConfig.StateMachineRepository().GetStateMachineByNameAndTenantId(stateInstruction.StateMachineName(), | |||||
| stateInstruction.TenantId()) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| state = stateMachine.States()[stateInstruction.StateName()] | |||||
| } | |||||
| stateType := state.Type() | |||||
| router := s.stateRouters[stateType] | |||||
| var interceptors []StateRouterInterceptor | |||||
| if interceptAbleStateRouter, ok := router.(InterceptAbleStateRouter); ok { | |||||
| interceptors = interceptAbleStateRouter.StateRouterInterceptor() | |||||
| } | |||||
| var executedInterceptors []StateRouterInterceptor | |||||
| var exception error | |||||
| instruction, exception := func() (Instruction, error) { | |||||
| if interceptors == nil || len(executedInterceptors) == 0 { | |||||
| executedInterceptors = make([]StateRouterInterceptor, 0, len(interceptors)) | |||||
| for _, interceptor := range interceptors { | |||||
| executedInterceptors = append(executedInterceptors, interceptor) | |||||
| err := interceptor.PreRoute(ctx, processContext, state) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| } | |||||
| instruction, err := router.Route(ctx, processContext, state) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return instruction, nil | |||||
| }() | |||||
| if interceptors == nil || len(executedInterceptors) == 0 { | |||||
| for i := len(executedInterceptors) - 1; i >= 0; i-- { | |||||
| err := executedInterceptors[i].PostRoute(ctx, processContext, instruction, exception) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| // if 'Succeed' or 'Fail' State did not configured, we must end the state machine | |||||
| if instruction == nil && !stateInstruction.End() { | |||||
| err := EndStateMachine(ctx, processContext) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| } | |||||
| return instruction, nil | |||||
| } | |||||
| func (s *StateMachineProcessRouter) InitDefaultStateRouters() { | |||||
| if s.stateRouters == nil || len(s.stateRouters) == 0 { | |||||
| s.stateRouters = make(map[string]StateRouter) | |||||
| taskStateRouter := &TaskStateRouter{} | |||||
| s.stateRouters[constant.StateTypeServiceTask] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeScriptTask] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeChoice] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeCompensationTrigger] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeSubStateMachine] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeCompensateSubMachine] = taskStateRouter | |||||
| s.stateRouters[constant.StateTypeLoopStart] = taskStateRouter | |||||
| endStateRouter := &EndStateRouter{} | |||||
| s.stateRouters[constant.StateTypeSucceed] = endStateRouter | |||||
| s.stateRouters[constant.StateTypeFail] = endStateRouter | |||||
| } | |||||
| } | |||||
| func (s *StateMachineProcessRouter) StateRouters() map[string]StateRouter { | |||||
| return s.stateRouters | |||||
| } | |||||
| func (s *StateMachineProcessRouter) SetStateRouters(stateRouters map[string]StateRouter) { | |||||
| s.stateRouters = stateRouters | |||||
| } | |||||
| @@ -0,0 +1,118 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "sync" | |||||
| ) | |||||
| type StateHandler interface { | |||||
| State() string | |||||
| ProcessHandler | |||||
| } | |||||
| type InterceptAbleStateHandler interface { | |||||
| StateHandler | |||||
| StateHandlerInterceptorList() []StateHandlerInterceptor | |||||
| RegistryStateHandlerInterceptor(stateHandlerInterceptor StateHandlerInterceptor) | |||||
| } | |||||
| type ProcessHandler interface { | |||||
| Process(ctx context.Context, processContext ProcessContext) error | |||||
| } | |||||
| type StateHandlerInterceptor interface { | |||||
| PreProcess(ctx context.Context, processContext ProcessContext) error | |||||
| PostProcess(ctx context.Context, processContext ProcessContext) error | |||||
| Match(stateType string) bool | |||||
| } | |||||
| type StateMachineProcessHandler struct { | |||||
| mp map[string]StateHandler | |||||
| mu sync.RWMutex | |||||
| } | |||||
| func NewStateMachineProcessHandler() *StateMachineProcessHandler { | |||||
| return &StateMachineProcessHandler{ | |||||
| mp: make(map[string]StateHandler), | |||||
| } | |||||
| } | |||||
| func (s *StateMachineProcessHandler) Process(ctx context.Context, processContext ProcessContext) error { | |||||
| stateInstruction, _ := processContext.GetInstruction().(StateInstruction) | |||||
| state, err := stateInstruction.GetState(processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| stateType := state.Type() | |||||
| stateHandler := s.GetStateHandler(stateType) | |||||
| if stateHandler == nil { | |||||
| return errors.New("Not support [" + stateType + "] state handler") | |||||
| } | |||||
| interceptAbleStateHandler, ok := stateHandler.(InterceptAbleStateHandler) | |||||
| var stateHandlerInterceptorList []StateHandlerInterceptor | |||||
| if ok { | |||||
| stateHandlerInterceptorList = interceptAbleStateHandler.StateHandlerInterceptorList() | |||||
| } | |||||
| if stateHandlerInterceptorList != nil && len(stateHandlerInterceptorList) > 0 { | |||||
| for _, stateHandlerInterceptor := range stateHandlerInterceptorList { | |||||
| err = stateHandlerInterceptor.PreProcess(ctx, processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| } | |||||
| err = stateHandler.Process(ctx, processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if stateHandlerInterceptorList != nil && len(stateHandlerInterceptorList) > 0 { | |||||
| for _, stateHandlerInterceptor := range stateHandlerInterceptorList { | |||||
| err = stateHandlerInterceptor.PostProcess(ctx, processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (s *StateMachineProcessHandler) GetStateHandler(stateType string) StateHandler { | |||||
| s.mu.RLock() | |||||
| defer s.mu.RUnlock() | |||||
| return s.mp[stateType] | |||||
| } | |||||
| func (s *StateMachineProcessHandler) RegistryStateHandler(stateType string, stateHandler StateHandler) { | |||||
| s.mu.Lock() | |||||
| defer s.mu.Unlock() | |||||
| if s.mp == nil { | |||||
| s.mp = make(map[string]StateHandler) | |||||
| } | |||||
| s.mp[stateType] = stateHandler | |||||
| } | |||||
| @@ -0,0 +1,168 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| sagaState "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type EndStateRouter struct { | |||||
| } | |||||
| func (e EndStateRouter) Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) { | |||||
| return nil, nil | |||||
| } | |||||
| type TaskStateRouter struct { | |||||
| } | |||||
| func (t TaskStateRouter) Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) { | |||||
| stateInstruction, _ := processContext.GetInstruction().(StateInstruction) | |||||
| if stateInstruction.End() { | |||||
| log.Infof("StateInstruction is ended, Stop the StateMachine executing. StateMachine[%s] Current State[%s]", | |||||
| stateInstruction.StateMachineName(), stateInstruction.StateName()) | |||||
| } | |||||
| // check if in loop async condition | |||||
| isLoop, ok := processContext.GetVariable(constant.VarNameIsLoopState).(bool) | |||||
| if ok && isLoop { | |||||
| log.Infof("StateMachine[%s] Current State[%s] is in loop async condition, skip route processing.", | |||||
| stateInstruction.StateMachineName(), stateInstruction.StateName()) | |||||
| return nil, nil | |||||
| } | |||||
| // The current CompensationTriggerState can mark the compensation process is started and perform compensation | |||||
| // route processing. | |||||
| compensationTriggerState, ok := processContext.GetVariable(constant.VarNameCurrentCompensateTriggerState).(statelang.State) | |||||
| if ok { | |||||
| return t.compensateRoute(ctx, processContext, compensationTriggerState) | |||||
| } | |||||
| // There is an exception route, indicating that an exception is thrown, and the exception route is prioritized. | |||||
| next := processContext.GetVariable(constant.VarNameCurrentExceptionRoute).(string) | |||||
| if next != "" { | |||||
| processContext.RemoveVariable(constant.VarNameCurrentExceptionRoute) | |||||
| } else { | |||||
| next = state.Next() | |||||
| } | |||||
| // If next is empty, the state selected by the Choice state was taken. | |||||
| if next == "" && processContext.HasVariable(constant.VarNameCurrentChoice) { | |||||
| next = processContext.GetVariable(constant.VarNameCurrentChoice).(string) | |||||
| processContext.RemoveVariable(constant.VarNameCurrentChoice) | |||||
| } | |||||
| if next == "" { | |||||
| return nil, nil | |||||
| } | |||||
| stateMachine := state.StateMachine() | |||||
| nextState := stateMachine.State(next) | |||||
| if nextState == nil { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "Next state["+next+"] is not exits", nil) | |||||
| } | |||||
| stateInstruction.SetStateName(next) | |||||
| if nil != GetLoopConfig(ctx, processContext, nextState) { | |||||
| stateInstruction.SetTemporaryState(sagaState.NewLoopStartStateImpl()) | |||||
| } | |||||
| return stateInstruction, nil | |||||
| } | |||||
| func (t *TaskStateRouter) compensateRoute(ctx context.Context, processContext ProcessContext, | |||||
| compensationTriggerState statelang.State) (Instruction, error) { | |||||
| //If there is already a compensation state that has been executed, | |||||
| // it is judged whether it is wrong or unsuccessful, | |||||
| // and the compensation process is interrupted. | |||||
| isFirstCompensationStateStart := processContext.GetVariable(constant.VarNameFirstCompensationStateStarted).(bool) | |||||
| if isFirstCompensationStateStart { | |||||
| exception := processContext.GetVariable(constant.VarNameCurrentException).(error) | |||||
| if exception != nil { | |||||
| return nil, EndStateMachine(ctx, processContext) | |||||
| } | |||||
| stateInstance := processContext.GetVariable(constant.VarNameStateInst).(statelang.StateInstance) | |||||
| if stateInstance != nil && statelang.SU != stateInstance.Status() { | |||||
| return nil, EndStateMachine(ctx, processContext) | |||||
| } | |||||
| } | |||||
| stateStackToBeCompensated := GetCurrentCompensationHolder(ctx, processContext, true).StateStackNeedCompensation() | |||||
| if stateStackToBeCompensated != nil { | |||||
| stateToBeCompensated := stateStackToBeCompensated.Pop().(statelang.StateInstance) | |||||
| stateMachine := processContext.GetVariable(constant.VarNameStateMachine).(statelang.StateMachine) | |||||
| state := stateMachine.State(GetOriginStateName(stateToBeCompensated)) | |||||
| if taskState, ok := state.(sagaState.AbstractTaskState); ok { | |||||
| instruction := processContext.GetInstruction().(StateInstruction) | |||||
| var compensateState statelang.State | |||||
| compensateStateName := taskState.CompensateState() | |||||
| if len(compensateStateName) != 0 { | |||||
| compensateState = stateMachine.State(compensateStateName) | |||||
| } | |||||
| if subStateMachine, ok := state.(sagaState.SubStateMachine); compensateState == nil && ok { | |||||
| compensateState = subStateMachine.CompensateStateImpl() | |||||
| instruction.SetTemporaryState(compensateState) | |||||
| } | |||||
| if compensateState == nil { | |||||
| return nil, EndStateMachine(ctx, processContext) | |||||
| } | |||||
| instruction.SetStateName(compensateState.Name()) | |||||
| GetCurrentCompensationHolder(ctx, processContext, true).AddToBeCompensatedState(compensateState.Name(), | |||||
| stateToBeCompensated) | |||||
| hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||||
| hierarchicalProcessContext.SetVariableLocally(constant.VarNameFirstCompensationStateStarted, true) | |||||
| if _, ok := compensateState.(sagaState.CompensateSubStateMachineState); ok { | |||||
| hierarchicalProcessContext = processContext.(HierarchicalProcessContext) | |||||
| hierarchicalProcessContext.SetVariableLocally( | |||||
| compensateState.Name()+constant.VarNameSubMachineParentId, | |||||
| GenerateParentId(stateToBeCompensated)) | |||||
| } | |||||
| return instruction, nil | |||||
| } | |||||
| } | |||||
| processContext.RemoveVariable(constant.VarNameCurrentCompensateTriggerState) | |||||
| compensationTriggerStateNext := compensationTriggerState.Next() | |||||
| if compensationTriggerStateNext == "" { | |||||
| return nil, EndStateMachine(ctx, processContext) | |||||
| } | |||||
| instruction := processContext.GetInstruction().(StateInstruction) | |||||
| instruction.SetStateName(compensationTriggerStateNext) | |||||
| return instruction, nil | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||||
| "sync" | |||||
| ) | |||||
| type StateMachineConfig interface { | |||||
| StateLogRepository() StateLogRepository | |||||
| StateMachineRepository() StateMachineRepository | |||||
| StateLogStore() StateLogStore | |||||
| StateLangStore() StateLangStore | |||||
| ExpressionFactoryManager() expr.ExpressionFactoryManager | |||||
| ExpressionResolver() expr.ExpressionResolver | |||||
| SeqGenerator() sequence.SeqGenerator | |||||
| StatusDecisionStrategy() StatusDecisionStrategy | |||||
| EventPublisher() EventPublisher | |||||
| AsyncEventPublisher() EventPublisher | |||||
| ServiceInvokerManager() invoker.ServiceInvokerManager | |||||
| ScriptInvokerManager() invoker.ScriptInvokerManager | |||||
| CharSet() string | |||||
| DefaultTenantId() string | |||||
| TransOperationTimeout() int | |||||
| ServiceInvokeTimeout() int | |||||
| ComponentLock() *sync.Mutex | |||||
| } | |||||
| @@ -0,0 +1,58 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| type StateMachineEngine interface { | |||||
| // Start starts a state machine instance | |||||
| Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||||
| // StartAsync start a state machine instance asynchronously | |||||
| StartAsync(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}, | |||||
| callback CallBack) (statelang.StateMachineInstance, error) | |||||
| // StartWithBusinessKey starts a state machine instance with a business key | |||||
| StartWithBusinessKey(ctx context.Context, stateMachineName string, tenantId string, businessKey string, | |||||
| startParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||||
| // StartWithBusinessKeyAsync starts a state machine instance with a business key asynchronously | |||||
| StartWithBusinessKeyAsync(ctx context.Context, stateMachineName string, tenantId string, businessKey string, | |||||
| startParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) | |||||
| // Forward restart a failed state machine instance | |||||
| Forward(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||||
| // ForwardAsync restart a failed state machine instance asynchronously | |||||
| ForwardAsync(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) | |||||
| // Compensate compensate a state machine instance | |||||
| Compensate(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||||
| // CompensateAsync compensate a state machine instance asynchronously | |||||
| CompensateAsync(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}, callback CallBack) (statelang.StateMachineInstance, error) | |||||
| // SkipAndForward skips the current failed state instance and restarts the state machine instance | |||||
| SkipAndForward(ctx context.Context, stateMachineInstId string, replaceParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||||
| // SkipAndForwardAsync skips the current failed state instance and restarts the state machine instance asynchronously | |||||
| SkipAndForwardAsync(ctx context.Context, stateMachineInstId string, callback CallBack) (statelang.StateMachineInstance, error) | |||||
| // GetStateMachineConfig gets the state machine configurations | |||||
| GetStateMachineConfig() StateMachineConfig | |||||
| // ReloadStateMachineInstance reloads a state machine instance | |||||
| ReloadStateMachineInstance(ctx context.Context, instId string) (statelang.StateMachineInstance, error) | |||||
| } | |||||
| type CallBack interface { | |||||
| OnFinished(ctx context.Context, context ProcessContext, stateMachineInstance statelang.StateMachineInstance) | |||||
| OnError(ctx context.Context, context ProcessContext, stateMachineInstance statelang.StateMachineInstance, err error) | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "testing" | |||||
| ) | |||||
| func TestEngine(t *testing.T) { | |||||
| } | |||||
| func TestSimpleStateMachine(t *testing.T) { | |||||
| engine := NewProcessCtrlStateMachineEngine() | |||||
| engine.Start(context.Background(), "simpleStateMachine", "tenantId", nil) | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "io" | |||||
| ) | |||||
| type StateLogRepository interface { | |||||
| GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateMachineInstance, error) | |||||
| GetStateMachineInstanceByBusinessKey(businessKey string, tenantId string) (statelang.StateInstance, error) | |||||
| GetStateMachineInstanceByParentId(parentId string) ([]statelang.StateMachineInstance, error) | |||||
| GetStateInstance(stateInstanceId string, stateMachineInstanceId string) (statelang.StateInstance, error) | |||||
| GetStateInstanceListByMachineInstanceId(stateMachineInstanceId string) ([]statelang.StateInstance, error) | |||||
| } | |||||
| type StateLogStore interface { | |||||
| RecordStateMachineStarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||||
| RecordStateMachineFinished(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||||
| RecordStateMachineRestarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||||
| RecordStateStarted(ctx context.Context, stateInstance statelang.StateInstance, context ProcessContext) error | |||||
| RecordStateFinished(ctx context.Context, stateInstance statelang.StateInstance, context ProcessContext) error | |||||
| GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateMachineInstance, error) | |||||
| GetStateMachineInstanceByBusinessKey(businessKey string, tenantId string) (statelang.StateMachineInstance, error) | |||||
| GetStateMachineInstanceByParentId(parentId string) ([]statelang.StateMachineInstance, error) | |||||
| GetStateInstance(stateInstanceId string, stateMachineInstanceId string) (statelang.StateInstance, error) | |||||
| GetStateInstanceListByMachineInstanceId(stateMachineInstanceId string) ([]statelang.StateInstance, error) | |||||
| ClearUp(context ProcessContext) | |||||
| } | |||||
| type StateMachineRepository interface { | |||||
| GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) | |||||
| GetStateMachineByNameAndTenantId(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||||
| GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||||
| RegistryStateMachine(statelang.StateMachine) error | |||||
| RegistryStateMachineByReader(reader io.Reader) error | |||||
| } | |||||
| type StateLangStore interface { | |||||
| GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) | |||||
| GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||||
| StoreStateMachine(stateMachine statelang.StateMachine) error | |||||
| } | |||||
| @@ -0,0 +1,256 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type StatusDecisionStrategy interface { | |||||
| // DecideOnEndState Determine state machine execution status when executing to EndState | |||||
| DecideOnEndState(ctx context.Context, processContext ProcessContext, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error) error | |||||
| // DecideOnTaskStateFail Determine state machine execution status when executing TaskState error | |||||
| DecideOnTaskStateFail(ctx context.Context, processContext ProcessContext, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error) error | |||||
| // DecideMachineForwardExecutionStatus Determine the forward execution state of the state machine | |||||
| DecideMachineForwardExecutionStatus(ctx context.Context, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) error | |||||
| } | |||||
| type DefaultStatusDecisionStrategy struct { | |||||
| } | |||||
| func NewDefaultStatusDecisionStrategy() *DefaultStatusDecisionStrategy { | |||||
| return &DefaultStatusDecisionStrategy{} | |||||
| } | |||||
| func (d DefaultStatusDecisionStrategy) DecideOnEndState(ctx context.Context, processContext ProcessContext, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error) error { | |||||
| if statelang.RU == stateMachineInstance.CompensationStatus() { | |||||
| compensationHolder := GetCurrentCompensationHolder(ctx, processContext, true) | |||||
| if err := decideMachineCompensateStatus(ctx, stateMachineInstance, compensationHolder); err != nil { | |||||
| return err | |||||
| } | |||||
| } else { | |||||
| failEndStateFlag, ok := processContext.GetVariable(constant.VarNameFailEndStateFlag).(bool) | |||||
| if !ok { | |||||
| failEndStateFlag = false | |||||
| } | |||||
| if _, err := decideMachineForwardExecutionStatus(ctx, stateMachineInstance, exp, failEndStateFlag); err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| if stateMachineInstance.CompensationStatus() != "" && constant.OperationNameForward == | |||||
| processContext.GetVariable(constant.VarNameOperationName).(string) && statelang.SU == stateMachineInstance.Status() { | |||||
| stateMachineInstance.SetCompensationStatus(statelang.FA) | |||||
| } | |||||
| log.Debugf("StateMachine Instance[id:%s,name:%s] execute finish with status[%s], compensation status [%s].", | |||||
| stateMachineInstance.ID(), stateMachineInstance.StateMachine().Name(), | |||||
| stateMachineInstance.Status(), stateMachineInstance.CompensationStatus()) | |||||
| return nil | |||||
| } | |||||
| func decideMachineCompensateStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, compensationHolder *CompensationHolder) error { | |||||
| if stateMachineInstance.Status() == "" || statelang.RU == stateMachineInstance.Status() { | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| } | |||||
| if !compensationHolder.StateStackNeedCompensation().Empty() { | |||||
| hasCompensateSUorUN := false | |||||
| compensationHolder.StatesForCompensation().Range( | |||||
| func(key, value any) bool { | |||||
| stateInstance, ok := value.(statelang.StateInstance) | |||||
| if !ok { | |||||
| return false | |||||
| } | |||||
| if statelang.UN == stateInstance.Status() || statelang.SU == stateInstance.Status() { | |||||
| hasCompensateSUorUN = true | |||||
| return true | |||||
| } | |||||
| return false | |||||
| }) | |||||
| if hasCompensateSUorUN { | |||||
| stateMachineInstance.SetCompensationStatus(statelang.UN) | |||||
| } else { | |||||
| stateMachineInstance.SetCompensationStatus(statelang.FA) | |||||
| } | |||||
| } else { | |||||
| hasCompensateError := false | |||||
| compensationHolder.StatesForCompensation().Range( | |||||
| func(key, value any) bool { | |||||
| stateInstance, ok := value.(statelang.StateInstance) | |||||
| if !ok { | |||||
| return false | |||||
| } | |||||
| if statelang.SU != stateInstance.Status() { | |||||
| hasCompensateError = true | |||||
| return true | |||||
| } | |||||
| return false | |||||
| }) | |||||
| if hasCompensateError { | |||||
| stateMachineInstance.SetCompensationStatus(statelang.UN) | |||||
| } else { | |||||
| stateMachineInstance.SetCompensationStatus(statelang.SU) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func decideMachineForwardExecutionStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) (bool, error) { | |||||
| result := false | |||||
| if stateMachineInstance.Status() == "" || statelang.RU == stateMachineInstance.Status() { | |||||
| result = true | |||||
| stateList := stateMachineInstance.StateList() | |||||
| //Determine the final state of the entire state machine based on the state of each StateInstance | |||||
| setMachineStatusBasedOnStateListAndException(stateMachineInstance, stateList, exp) | |||||
| if specialPolicy && statelang.SU == stateMachineInstance.Status() { | |||||
| for _, stateInstance := range stateMachineInstance.StateList() { | |||||
| if !stateInstance.IsIgnoreStatus() && (stateInstance.IsForUpdate() || stateInstance.IsForCompensation()) { | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| break | |||||
| } | |||||
| } | |||||
| if statelang.SU == stateMachineInstance.Status() { | |||||
| stateMachineInstance.SetStatus(statelang.FA) | |||||
| } | |||||
| } | |||||
| } | |||||
| return result, nil | |||||
| } | |||||
| func setMachineStatusBasedOnStateListAndException(stateMachineInstance statelang.StateMachineInstance, | |||||
| stateList []statelang.StateInstance, exp error) { | |||||
| hasSetStatus := false | |||||
| hasSuccessUpdateService := false | |||||
| if stateList != nil && len(stateList) > 0 { | |||||
| hasUnsuccessService := false | |||||
| for i := len(stateList) - 1; i >= 0; i-- { | |||||
| stateInstance := stateList[i] | |||||
| if stateInstance.IsIgnoreStatus() || stateInstance.IsForCompensation() { | |||||
| continue | |||||
| } | |||||
| if statelang.UN == stateInstance.Status() { | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| hasSetStatus = true | |||||
| } else if statelang.SU == stateInstance.Status() { | |||||
| if constant.StateTypeServiceTask == stateInstance.Type() { | |||||
| if stateInstance.IsForUpdate() && !stateInstance.IsForCompensation() { | |||||
| hasSuccessUpdateService = true | |||||
| } | |||||
| } | |||||
| } else if statelang.SK == stateInstance.Status() { | |||||
| // ignore | |||||
| } else { | |||||
| hasUnsuccessService = true | |||||
| } | |||||
| } | |||||
| if !hasSetStatus && hasUnsuccessService { | |||||
| if hasSuccessUpdateService { | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| } else { | |||||
| stateMachineInstance.SetStatus(statelang.FA) | |||||
| } | |||||
| hasSetStatus = true | |||||
| } | |||||
| } | |||||
| if !hasSetStatus { | |||||
| setMachineStatusBasedOnException(stateMachineInstance, exp, hasSuccessUpdateService) | |||||
| } | |||||
| } | |||||
| func setMachineStatusBasedOnException(stateMachineInstance statelang.StateMachineInstance, exp error, hasSuccessUpdateService bool) { | |||||
| if exp == nil { | |||||
| log.Debugf("No error found, setting StateMachineInstance[id:%s] status to SU", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.SU) | |||||
| return | |||||
| } | |||||
| var engineExp *exception.EngineExecutionException | |||||
| if errors.As(exp, &engineExp) && engineExp.ErrCode == constant.FrameworkErrorCodeStateMachineExecutionTimeout { | |||||
| log.Warnf("Execution timeout detected, setting StateMachineInstance[id:%s] status to UN", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| return | |||||
| } | |||||
| if hasSuccessUpdateService { | |||||
| log.Infof("Has successful update service, setting StateMachineInstance[id:%s] status to UN", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| return | |||||
| } | |||||
| netType := GetNetExceptionType(exp) | |||||
| switch netType { | |||||
| case constant.ConnectException, constant.ConnectTimeoutException, constant.NotNetException: | |||||
| log.Warnf("Detected network connect issue, setting StateMachineInstance[id:%s] status to FA", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.FA) | |||||
| case constant.ReadTimeoutException: | |||||
| log.Warnf("Detected read timeout, setting StateMachineInstance[id:%s] status to UN", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.UN) | |||||
| default: | |||||
| //Default failure | |||||
| log.Errorf("Unknown exception type, setting StateMachineInstance[id:%s] status to FA", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetStatus(statelang.FA) | |||||
| } | |||||
| } | |||||
| func (d DefaultStatusDecisionStrategy) DecideOnTaskStateFail(ctx context.Context, processContext ProcessContext, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error) error { | |||||
| log.Debugf("Starting DecideOnTaskStateFail for StateMachineInstance[id:%s]", stateMachineInstance.ID()) | |||||
| result, err := decideMachineForwardExecutionStatus(ctx, stateMachineInstance, exp, true) | |||||
| if err != nil { | |||||
| log.Errorf("DecideMachineForwardExecutionStatus failed: %v", err) | |||||
| return err | |||||
| } | |||||
| if !result { | |||||
| log.Warnf("Forward execution result is false, setting compensation status UN for StateMachineInstance[id:%s]", stateMachineInstance.ID()) | |||||
| stateMachineInstance.SetCompensationStatus(statelang.UN) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (d DefaultStatusDecisionStrategy) DecideMachineForwardExecutionStatus(ctx context.Context, | |||||
| stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) error { | |||||
| log.Debugf("Starting DecideMachineForwardExecutionStatus for StateMachineInstance[id:%s], specialPolicy: %v", stateMachineInstance.ID(), specialPolicy) | |||||
| _, err := decideMachineForwardExecutionStatus(ctx, stateMachineInstance, exp, specialPolicy) | |||||
| if err != nil { | |||||
| log.Errorf("DecideMachineForwardExecutionStatus failed: %v", err) | |||||
| } | |||||
| return err | |||||
| } | |||||
| @@ -0,0 +1,111 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package core | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| // ProcessContextBuilder process_ctrl builder | |||||
| type ProcessContextBuilder struct { | |||||
| processContext ProcessContext | |||||
| } | |||||
| func NewProcessContextBuilder() *ProcessContextBuilder { | |||||
| processContextImpl := NewProcessContextImpl() | |||||
| return &ProcessContextBuilder{processContextImpl} | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithProcessType(processType process.ProcessType) *ProcessContextBuilder { | |||||
| p.processContext.SetVariable(constant.VarNameProcessType, processType) | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithOperationName(operationName string) *ProcessContextBuilder { | |||||
| p.processContext.SetVariable(constant.VarNameOperationName, operationName) | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithAsyncCallback(callBack CallBack) *ProcessContextBuilder { | |||||
| if callBack != nil { | |||||
| p.processContext.SetVariable(constant.VarNameAsyncCallback, callBack) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithInstruction(instruction Instruction) *ProcessContextBuilder { | |||||
| if instruction != nil { | |||||
| p.processContext.SetInstruction(instruction) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithStateMachineInstance(stateMachineInstance statelang.StateMachineInstance) *ProcessContextBuilder { | |||||
| if stateMachineInstance != nil { | |||||
| p.processContext.SetVariable(constant.VarNameStateMachineInst, stateMachineInstance) | |||||
| p.processContext.SetVariable(constant.VarNameStateMachine, stateMachineInstance.StateMachine()) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithStateMachineEngine(stateMachineEngine StateMachineEngine) *ProcessContextBuilder { | |||||
| if stateMachineEngine != nil { | |||||
| p.processContext.SetVariable(constant.VarNameStateMachineEngine, stateMachineEngine) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithStateMachineConfig(stateMachineConfig StateMachineConfig) *ProcessContextBuilder { | |||||
| if stateMachineConfig != nil { | |||||
| p.processContext.SetVariable(constant.VarNameStateMachineConfig, stateMachineConfig) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithStateMachineContextVariables(contextMap map[string]interface{}) *ProcessContextBuilder { | |||||
| if contextMap != nil { | |||||
| p.processContext.SetVariable(constant.VarNameStateMachineContext, contextMap) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithIsAsyncExecution(async bool) *ProcessContextBuilder { | |||||
| p.processContext.SetVariable(constant.VarNameIsAsyncExecution, async) | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) WithStateInstance(state statelang.StateInstance) *ProcessContextBuilder { | |||||
| if state != nil { | |||||
| p.processContext.SetVariable(constant.VarNameStateInst, state) | |||||
| } | |||||
| return p | |||||
| } | |||||
| func (p *ProcessContextBuilder) Build() ProcessContext { | |||||
| return p.processContext | |||||
| } | |||||
| @@ -0,0 +1,79 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package exception | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/util/errors" | |||||
| ) | |||||
| type EngineExecutionException struct { | |||||
| errors.SeataError | |||||
| stateName string | |||||
| stateMachineName string | |||||
| stateMachineInstanceId string | |||||
| stateInstanceId string | |||||
| ErrCode string | |||||
| } | |||||
| func (e *EngineExecutionException) Error() string { | |||||
| return fmt.Sprintf("EngineExecutionException: %s", e.ErrCode) | |||||
| } | |||||
| func NewEngineExecutionException(code errors.TransactionErrorCode, msg string, parent error) *EngineExecutionException { | |||||
| seataError := errors.New(code, msg, parent) | |||||
| return &EngineExecutionException{ | |||||
| SeataError: *seataError, | |||||
| } | |||||
| } | |||||
| func (e *EngineExecutionException) StateName() string { | |||||
| return e.stateName | |||||
| } | |||||
| func (e *EngineExecutionException) SetStateName(stateName string) { | |||||
| e.stateName = stateName | |||||
| } | |||||
| func (e *EngineExecutionException) StateMachineName() string { | |||||
| return e.stateMachineName | |||||
| } | |||||
| func (e *EngineExecutionException) SetStateMachineName(stateMachineName string) { | |||||
| e.stateMachineName = stateMachineName | |||||
| } | |||||
| func (e *EngineExecutionException) StateMachineInstanceId() string { | |||||
| return e.stateMachineInstanceId | |||||
| } | |||||
| func (e *EngineExecutionException) SetStateMachineInstanceId(stateMachineInstanceId string) { | |||||
| e.stateMachineInstanceId = stateMachineInstanceId | |||||
| } | |||||
| func (e *EngineExecutionException) StateInstanceId() string { | |||||
| return e.stateInstanceId | |||||
| } | |||||
| func (e *EngineExecutionException) SetStateInstanceId(stateInstanceId string) { | |||||
| e.stateInstanceId = stateInstanceId | |||||
| } | |||||
| type ForwardInvalidException struct { | |||||
| EngineExecutionException | |||||
| } | |||||
| @@ -0,0 +1,96 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "github.com/google/cel-go/cel" | |||||
| ) | |||||
| type CELExpression struct { | |||||
| env *cel.Env | |||||
| program cel.Program | |||||
| expression string | |||||
| } | |||||
| // This is used to make sure that the CELExpression implements the Expression interface. | |||||
| var _ Expression = (*CELExpression)(nil) | |||||
| // NewCELExpression creates a new CELExpression instance | |||||
| // by compiling the provided expression. | |||||
| // how to use cel: https://codelabs.developers.google.com/codelabs/cel-go | |||||
| func NewCELExpression(expression string) (*CELExpression, error) { | |||||
| // Create the standard environment. | |||||
| env, err := cel.NewEnv( | |||||
| cel.Variable( | |||||
| "elContext", cel.DynType, | |||||
| ), | |||||
| ) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| // Check that the expression compiles and returns a String. | |||||
| ast, issues := env.Compile(expression) | |||||
| // Report syntax errors, if present. | |||||
| if issues != nil && issues.Err() != nil { | |||||
| return nil, issues.Err() | |||||
| } | |||||
| // Type-check the expression ofr correctness. | |||||
| checkedAst, issues := env.Check(ast) | |||||
| if issues.Err() != nil { | |||||
| return nil, issues.Err() | |||||
| } | |||||
| program, err := env.Program(checkedAst) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| CELExpression := &CELExpression{ | |||||
| env: env, | |||||
| program: program, | |||||
| expression: expression, | |||||
| } | |||||
| return CELExpression, nil | |||||
| } | |||||
| // Value evaluates the expression with the provided context and returns the result. | |||||
| func (c *CELExpression) Value(elContext any) any { | |||||
| result, _, err := c.program.Eval(map[string]any{ | |||||
| "elContext": elContext, | |||||
| }) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return result.Value() | |||||
| } | |||||
| // TODO: I think this is not needed. | |||||
| // I see seata-java doesn't use this method. | |||||
| // Do we need to implement this? | |||||
| func (c *CELExpression) SetValue(val any, elContext any) { | |||||
| panic("implement me") | |||||
| } | |||||
| // ExpressionString returns the expression string. | |||||
| func (c *CELExpression) ExpressionString() string { | |||||
| return c.expression | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| type CELExpressionFactory struct { | |||||
| expr *CELExpression | |||||
| } | |||||
| // This is used to make sure that CELExpressionFactory implements ExpressionFactory | |||||
| var _ ExpressionFactory = (*CELExpressionFactory)(nil) | |||||
| // NewCELExpressionFactory creates a new instance of CELExpressionFactory | |||||
| func NewCELExpressionFactory() *CELExpressionFactory { | |||||
| return &CELExpressionFactory{} | |||||
| } | |||||
| // CreateExpression creates a new instance of CELExpression | |||||
| func (f *CELExpressionFactory) CreateExpression(expression string) Expression { | |||||
| f.expr, _ = NewCELExpression(expression) | |||||
| return f.expr | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "testing" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func TestCelExpressionFactory(t *testing.T) { | |||||
| factory := NewCELExpressionFactory() | |||||
| expression := factory.CreateExpression("'Hello' + ' World!'") | |||||
| value := expression.Value(nil) | |||||
| assert.Equal(t, "Hello World!", value, "Expected 'Hello World!'") | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "testing" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func TestValueWithNil(t *testing.T) { | |||||
| expr, err := NewCELExpression("'Hello' + ' World!'") | |||||
| assert.NoError(t, err, "Error creating expression") | |||||
| value := expr.Value(nil) | |||||
| assert.Equal(t, "Hello World!", value, "Expected 'Hello World!'") | |||||
| } | |||||
| func TestValue(t *testing.T) { | |||||
| expr, err := NewCELExpression("elContext['name'] + ' World!'") | |||||
| assert.NoError(t, err, "Error creating expression") | |||||
| elContext := map[string]any{ | |||||
| "name": "Hello", | |||||
| } | |||||
| value := expr.Value(elContext) | |||||
| assert.Equal(t, "Hello World!", value, "Expected 'Hello World!'") | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| // expression interface | |||||
| type Expression interface { | |||||
| // get the value of the expression | |||||
| // elContext is the el context | |||||
| Value(elContext any) any | |||||
| SetValue(value any, elContext any) | |||||
| // return the expression string | |||||
| ExpressionString() string | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| type ExpressionFactory interface { | |||||
| CreateExpression(expression string) Expression | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "maps" | |||||
| "strings" | |||||
| ) | |||||
| const DefaultExpressionType = "Default" | |||||
| type ExpressionFactoryManager struct { | |||||
| expressionFactoryMap map[string]ExpressionFactory | |||||
| } | |||||
| func NewExpressionFactoryManager() *ExpressionFactoryManager { | |||||
| return &ExpressionFactoryManager{ | |||||
| expressionFactoryMap: make(map[string]ExpressionFactory), | |||||
| } | |||||
| } | |||||
| func (e *ExpressionFactoryManager) GetExpressionFactory(expressionType string) ExpressionFactory { | |||||
| if strings.TrimSpace(expressionType) == "" { | |||||
| expressionType = DefaultExpressionType | |||||
| } | |||||
| return e.expressionFactoryMap[expressionType] | |||||
| } | |||||
| func (e *ExpressionFactoryManager) SetExpressionFactoryMap(expressionFactoryMap map[string]ExpressionFactory) { | |||||
| maps.Copy(e.expressionFactoryMap, expressionFactoryMap) | |||||
| } | |||||
| func (e *ExpressionFactoryManager) PutExpressionFactory(expressionType string, factory ExpressionFactory) { | |||||
| e.expressionFactoryMap[expressionType] = factory | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "errors" | |||||
| "strings" | |||||
| ) | |||||
| type ExpressionResolver interface { | |||||
| Expression(expressionStr string) Expression | |||||
| ExpressionFactoryManager() ExpressionFactoryManager | |||||
| SetExpressionFactoryManager(expressionFactoryManager ExpressionFactoryManager) | |||||
| } | |||||
| type DefaultExpressionResolver struct { | |||||
| expressionFactoryManager ExpressionFactoryManager | |||||
| } | |||||
| func (resolver *DefaultExpressionResolver) Expression(expressionStr string) Expression { | |||||
| expressionStruct, err := parseExpressionStruct(expressionStr) | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| expressionFactory := resolver.expressionFactoryManager.GetExpressionFactory(expressionStruct.typ) | |||||
| if expressionFactory == nil { | |||||
| return nil | |||||
| } | |||||
| return expressionFactory.CreateExpression(expressionStruct.content) | |||||
| } | |||||
| func (resolver *DefaultExpressionResolver) ExpressionFactoryManager() ExpressionFactoryManager { | |||||
| return resolver.expressionFactoryManager | |||||
| } | |||||
| func (resolver *DefaultExpressionResolver) SetExpressionFactoryManager(expressionFactoryManager ExpressionFactoryManager) { | |||||
| resolver.expressionFactoryManager = expressionFactoryManager | |||||
| } | |||||
| type ExpressionStruct struct { | |||||
| typeStart int | |||||
| typeEnd int | |||||
| end int | |||||
| typ string | |||||
| content string | |||||
| } | |||||
| // old style: $type{content} | |||||
| // new style: $type.content | |||||
| func parseExpressionStruct(expressionStr string) (*ExpressionStruct, error) { | |||||
| eStruct := &ExpressionStruct{} | |||||
| eStruct.typeStart = strings.Index(expressionStr, "$") | |||||
| if eStruct.typeStart == -1 { | |||||
| return nil, errors.New("invalid expression") | |||||
| } | |||||
| dot := strings.Index(expressionStr, ".") | |||||
| leftBracket := strings.Index(expressionStr, "{") | |||||
| isOldEvaluatorStyle := false | |||||
| if eStruct.typeStart == 0 { | |||||
| if leftBracket < 0 && dot < 0 { | |||||
| return nil, errors.New("invalid expression") | |||||
| } | |||||
| // Backward compatible for structure: $expressionType{expressionContent} | |||||
| if leftBracket > 0 && (leftBracket < dot || dot < 0) { | |||||
| eStruct.typeEnd = leftBracket | |||||
| isOldEvaluatorStyle = true | |||||
| } | |||||
| if dot > 0 && (dot < leftBracket || leftBracket < 0) { | |||||
| eStruct.typeEnd = dot | |||||
| } | |||||
| } | |||||
| if eStruct.typeStart == 0 && leftBracket != -1 && leftBracket < dot { | |||||
| // Backward compatible for structure: $expressionType{expressionContent} | |||||
| eStruct.typeEnd = strings.Index(expressionStr, "{") | |||||
| isOldEvaluatorStyle = true | |||||
| } | |||||
| eStruct.typ = expressionStr[eStruct.typeStart+1 : eStruct.typeEnd] | |||||
| if isOldEvaluatorStyle { | |||||
| eStruct.end = strings.Index(expressionStr, "}") | |||||
| } else { | |||||
| eStruct.end = len(expressionStr) | |||||
| } | |||||
| eStruct.content = expressionStr[eStruct.typeEnd+1 : eStruct.end] | |||||
| return eStruct, nil | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| package expr | |||||
| import ( | |||||
| "testing" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func TestParseExpressionStruct(t *testing.T) { | |||||
| tests := []struct { | |||||
| expressionStr string | |||||
| expected *ExpressionStruct | |||||
| expectError bool | |||||
| }{ | |||||
| { | |||||
| expressionStr: "$type{content}", | |||||
| expected: &ExpressionStruct{ | |||||
| typeStart: 0, | |||||
| typeEnd: 5, | |||||
| typ: "type", | |||||
| end: 13, | |||||
| content: "content", | |||||
| }, | |||||
| expectError: false, | |||||
| }, | |||||
| { | |||||
| expressionStr: "$type.content", | |||||
| expected: &ExpressionStruct{ | |||||
| typeStart: 0, | |||||
| typeEnd: 5, | |||||
| typ: "type", | |||||
| end: 13, | |||||
| content: "content", | |||||
| }, | |||||
| expectError: false, | |||||
| }, | |||||
| { | |||||
| expressionStr: "invalid expression", | |||||
| expected: nil, | |||||
| expectError: true, | |||||
| }, | |||||
| } | |||||
| for _, test := range tests { | |||||
| result, err := parseExpressionStruct(test.expressionStr) | |||||
| if test.expectError { | |||||
| assert.Error(t, err, "Expected an error for input '%s'", test.expressionStr) | |||||
| } else { | |||||
| assert.NoError(t, err, "Did not expect an error for input '%s'", test.expressionStr) | |||||
| assert.NotNil(t, result, "Expected a non-nil result for input '%s'", test.expressionStr) | |||||
| assert.Equal(t, *test.expected, *result, "Expected result %+v, got %+v for input '%s'", test.expected, result, test.expressionStr) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package expr | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||||
| ) | |||||
| type SequenceExpression struct { | |||||
| seqGenerator sequence.SeqGenerator | |||||
| entity string | |||||
| rule string | |||||
| } | |||||
| func (s *SequenceExpression) SeqGenerator() sequence.SeqGenerator { | |||||
| return s.seqGenerator | |||||
| } | |||||
| func (s *SequenceExpression) SetSeqGenerator(seqGenerator sequence.SeqGenerator) { | |||||
| s.seqGenerator = seqGenerator | |||||
| } | |||||
| func (s *SequenceExpression) Entity() string { | |||||
| return s.entity | |||||
| } | |||||
| func (s *SequenceExpression) SetEntity(entity string) { | |||||
| s.entity = entity | |||||
| } | |||||
| func (s *SequenceExpression) Rule() string { | |||||
| return s.rule | |||||
| } | |||||
| func (s *SequenceExpression) SetRule(rule string) { | |||||
| s.rule = rule | |||||
| } | |||||
| func (s SequenceExpression) Value(vars map[string]any) any { | |||||
| return s.seqGenerator.GenerateId(s.entity, s.rule) | |||||
| } | |||||
| func (s SequenceExpression) SetValue(value any, elContext any) { | |||||
| } | |||||
| func (s SequenceExpression) ExpressionString() string { | |||||
| return s.entity + "|" + s.rule | |||||
| } | |||||
| @@ -0,0 +1,225 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "fmt" | |||||
| "reflect" | |||||
| "strings" | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type FuncInvoker struct { | |||||
| ServicesMapLock sync.Mutex | |||||
| servicesMap map[string]FuncService | |||||
| } | |||||
| func NewFuncInvoker() *FuncInvoker { | |||||
| return &FuncInvoker{ | |||||
| servicesMap: make(map[string]FuncService), | |||||
| } | |||||
| } | |||||
| func (f *FuncInvoker) RegisterService(serviceName string, service FuncService) { | |||||
| f.ServicesMapLock.Lock() | |||||
| defer f.ServicesMapLock.Unlock() | |||||
| f.servicesMap[serviceName] = service | |||||
| } | |||||
| func (f *FuncInvoker) GetService(serviceName string) FuncService { | |||||
| f.ServicesMapLock.Lock() | |||||
| defer f.ServicesMapLock.Unlock() | |||||
| return f.servicesMap[serviceName] | |||||
| } | |||||
| func (f *FuncInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) { | |||||
| serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl) | |||||
| FuncService := f.GetService(serviceTaskStateImpl.ServiceName()) | |||||
| if FuncService == nil { | |||||
| return nil, errors.New("no func service " + serviceTaskStateImpl.ServiceName() + " for service task state") | |||||
| } | |||||
| if serviceTaskStateImpl.IsAsync() { | |||||
| go func() { | |||||
| _, err := FuncService.CallMethod(serviceTaskStateImpl, input) | |||||
| if err != nil { | |||||
| log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(), serviceTaskStateImpl.ServiceMethod(), err.Error()) | |||||
| } | |||||
| }() | |||||
| return nil, nil | |||||
| } | |||||
| return FuncService.CallMethod(serviceTaskStateImpl, input) | |||||
| } | |||||
| func (f *FuncInvoker) Close(ctx context.Context) error { | |||||
| return nil | |||||
| } | |||||
| type FuncService interface { | |||||
| CallMethod(ServiceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) | |||||
| } | |||||
| type FuncServiceImpl struct { | |||||
| serviceName string | |||||
| methodLock sync.Mutex | |||||
| method any | |||||
| } | |||||
| func NewFuncService(serviceName string, method any) *FuncServiceImpl { | |||||
| return &FuncServiceImpl{ | |||||
| serviceName: serviceName, | |||||
| method: method, | |||||
| } | |||||
| } | |||||
| func (f *FuncServiceImpl) getMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl) (*reflect.Value, error) { | |||||
| method := serviceTaskStateImpl.Method() | |||||
| if method == nil { | |||||
| return f.initMethod(serviceTaskStateImpl) | |||||
| } | |||||
| return method, nil | |||||
| } | |||||
| func (f *FuncServiceImpl) prepareArguments(input []any) []reflect.Value { | |||||
| args := make([]reflect.Value, len(input)) | |||||
| for i, arg := range input { | |||||
| args[i] = reflect.ValueOf(arg) | |||||
| } | |||||
| return args | |||||
| } | |||||
| func (f *FuncServiceImpl) CallMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) { | |||||
| method, err := f.getMethod(serviceTaskStateImpl) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| args := f.prepareArguments(input) | |||||
| retryCountMap := make(map[state.Retry]int) | |||||
| for { | |||||
| res, err, shouldRetry := f.invokeMethod(method, args, serviceTaskStateImpl, retryCountMap) | |||||
| if !shouldRetry { | |||||
| if err != nil { | |||||
| return nil, errors.New("invoke service[" + serviceTaskStateImpl.ServiceName() + "]." + serviceTaskStateImpl.ServiceMethod() + " failed, err is " + err.Error()) | |||||
| } | |||||
| return res, nil | |||||
| } | |||||
| } | |||||
| } | |||||
| func (f *FuncServiceImpl) initMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl) (*reflect.Value, error) { | |||||
| methodName := serviceTaskStateImpl.ServiceMethod() | |||||
| f.methodLock.Lock() | |||||
| defer f.methodLock.Unlock() | |||||
| methodValue := reflect.ValueOf(f.method) | |||||
| if methodValue.IsZero() { | |||||
| return nil, errors.New("invalid method when func call, serviceName: " + f.serviceName) | |||||
| } | |||||
| if methodValue.Kind() == reflect.Func { | |||||
| serviceTaskStateImpl.SetMethod(&methodValue) | |||||
| return &methodValue, nil | |||||
| } | |||||
| method := methodValue.MethodByName(methodName) | |||||
| if method.IsZero() { | |||||
| return nil, errors.New("invalid method name when func call, serviceName: " + f.serviceName + ", methodName: " + methodName) | |||||
| } | |||||
| serviceTaskStateImpl.SetMethod(&method) | |||||
| return &method, nil | |||||
| } | |||||
| func (f *FuncServiceImpl) invokeMethod(method *reflect.Value, args []reflect.Value, serviceTaskStateImpl *state.ServiceTaskStateImpl, retryCountMap map[state.Retry]int) ([]reflect.Value, error, bool) { | |||||
| var res []reflect.Value | |||||
| var resErr error | |||||
| var shouldRetry bool | |||||
| defer func() { | |||||
| if r := recover(); r != nil { | |||||
| errStr := fmt.Sprintf("%v", r) | |||||
| retry := f.matchRetry(serviceTaskStateImpl, errStr) | |||||
| resErr = errors.New(errStr) | |||||
| if retry != nil { | |||||
| shouldRetry = f.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||||
| } | |||||
| } | |||||
| }() | |||||
| outs := method.Call(args) | |||||
| if err, ok := outs[len(outs)-1].Interface().(error); ok { | |||||
| resErr = err | |||||
| errStr := err.Error() | |||||
| retry := f.matchRetry(serviceTaskStateImpl, errStr) | |||||
| if retry != nil { | |||||
| shouldRetry = f.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||||
| } | |||||
| return nil, resErr, shouldRetry | |||||
| } | |||||
| res = outs | |||||
| return res, nil, false | |||||
| } | |||||
| func (f *FuncServiceImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry { | |||||
| if impl.Retry() != nil { | |||||
| for _, retry := range impl.Retry() { | |||||
| if retry.Exceptions() != nil { | |||||
| for _, exception := range retry.Exceptions() { | |||||
| if strings.Contains(str, exception) { | |||||
| return retry | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (f *FuncServiceImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool { | |||||
| attempt, exist := countMap[retry] | |||||
| if !exist { | |||||
| countMap[retry] = 0 | |||||
| } | |||||
| if attempt >= retry.MaxAttempt() { | |||||
| return false | |||||
| } | |||||
| interval := retry.IntervalSecond() | |||||
| backoffRate := retry.BackoffRate() | |||||
| curInterval := int64(interval * 1000) | |||||
| if attempt != 0 { | |||||
| curInterval = int64(interval * backoffRate * float64(attempt) * 1000) | |||||
| } | |||||
| log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s", | |||||
| impl.ServiceName(), impl.ServiceMethod(), curInterval, attempt, err) | |||||
| time.Sleep(time.Duration(curInterval) * time.Millisecond) | |||||
| countMap[retry] = attempt + 1 | |||||
| return true | |||||
| } | |||||
| @@ -0,0 +1,168 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "fmt" | |||||
| "testing" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| // struct's method test | |||||
| type mockFuncImpl struct { | |||||
| invokeCount int | |||||
| } | |||||
| func (m *mockFuncImpl) SayHelloRight(word string) (string, error) { | |||||
| m.invokeCount++ | |||||
| fmt.Println("invoke right") | |||||
| return word, nil | |||||
| } | |||||
| func (m *mockFuncImpl) SayHelloRightLater(word string, delay int) (string, error) { | |||||
| m.invokeCount++ | |||||
| if delay == m.invokeCount { | |||||
| fmt.Println("invoke right") | |||||
| return word, nil | |||||
| } | |||||
| fmt.Println("invoke fail") | |||||
| return "", errors.New("invoke failed") | |||||
| } | |||||
| func TestFuncInvokerInvokeSucceed(t *testing.T) { | |||||
| tests := []struct { | |||||
| name string | |||||
| input []any | |||||
| taskState state.ServiceTaskState | |||||
| expected string | |||||
| expectErr bool | |||||
| }{ | |||||
| { | |||||
| name: "Invoke Struct Succeed", | |||||
| input: []any{"hello"}, | |||||
| taskState: newFuncHelloServiceTaskState(), | |||||
| expected: "hello", | |||||
| expectErr: false, | |||||
| }, | |||||
| { | |||||
| name: "Invoke Struct In Retry", | |||||
| input: []any{"hello", 2}, | |||||
| taskState: newFuncHelloServiceTaskStateWithRetry(), | |||||
| expected: "hello", | |||||
| expectErr: false, | |||||
| }, | |||||
| } | |||||
| ctx := context.Background() | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| invoker := newFuncServiceInvoker() | |||||
| values, err := invoker.Invoke(ctx, tt.input, tt.taskState) | |||||
| if (err != nil) != tt.expectErr { | |||||
| t.Errorf("expected error: %v, got: %v", tt.expectErr, err) | |||||
| } | |||||
| if values == nil || len(values) == 0 { | |||||
| t.Fatal("no value in values") | |||||
| } | |||||
| if resultString, ok := values[0].Interface().(string); ok { | |||||
| if resultString != tt.expected { | |||||
| t.Errorf("expect %s, but got %s", tt.expected, resultString) | |||||
| } | |||||
| } else { | |||||
| t.Errorf("expected string, but got %v", values[0].Interface()) | |||||
| } | |||||
| if resultError, ok := values[1].Interface().(error); ok { | |||||
| if resultError != nil { | |||||
| t.Errorf("expect nil, but got %s", resultError) | |||||
| } | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| func TestFuncInvokerInvokeFailed(t *testing.T) { | |||||
| tests := []struct { | |||||
| name string | |||||
| input []any | |||||
| taskState state.ServiceTaskState | |||||
| expected string | |||||
| expectErr bool | |||||
| }{ | |||||
| { | |||||
| name: "Invoke Struct Failed In Retry", | |||||
| input: []any{"hello", 5}, | |||||
| taskState: newFuncHelloServiceTaskStateWithRetry(), | |||||
| expected: "", | |||||
| expectErr: true, | |||||
| }, | |||||
| } | |||||
| ctx := context.Background() | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| invoker := newFuncServiceInvoker() | |||||
| _, err := invoker.Invoke(ctx, tt.input, tt.taskState) | |||||
| if (err != nil) != tt.expectErr { | |||||
| t.Errorf("expected error: %v, got: %v", tt.expectErr, err) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| func newFuncServiceInvoker() ServiceInvoker { | |||||
| mockFuncInvoker := NewFuncInvoker() | |||||
| mockFuncService := &mockFuncImpl{} | |||||
| mockService := NewFuncService("hello", mockFuncService) | |||||
| mockFuncInvoker.RegisterService("hello", mockService) | |||||
| return mockFuncInvoker | |||||
| } | |||||
| func newFuncHelloServiceTaskState() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("hello") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("hello") | |||||
| serviceTaskStateImpl.SetServiceType("func") | |||||
| serviceTaskStateImpl.SetServiceMethod("SayHelloRight") | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| func newFuncHelloServiceTaskStateWithRetry() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("hello") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("hello") | |||||
| serviceTaskStateImpl.SetServiceType("func") | |||||
| serviceTaskStateImpl.SetServiceMethod("SayHelloRightLater") | |||||
| retryImpl := &state.RetryImpl{} | |||||
| retryImpl.SetExceptions([]string{"fail"}) | |||||
| retryImpl.SetIntervalSecond(1) | |||||
| retryImpl.SetMaxAttempt(3) | |||||
| retryImpl.SetBackoffRate(0.9) | |||||
| serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| @@ -0,0 +1,261 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| "google.golang.org/grpc" | |||||
| "reflect" | |||||
| "strings" | |||||
| "sync" | |||||
| "time" | |||||
| ) | |||||
| type GRPCInvoker struct { | |||||
| clients map[string]GRPCClient | |||||
| clientsMapLock sync.Mutex | |||||
| needClose bool | |||||
| } | |||||
| func NewGRPCInvoker() *GRPCInvoker { | |||||
| return &GRPCInvoker{ | |||||
| clients: make(map[string]GRPCClient), | |||||
| } | |||||
| } | |||||
| func (g *GRPCInvoker) NeedClose() bool { | |||||
| return g.needClose | |||||
| } | |||||
| func (g *GRPCInvoker) SetNeedClose(needClose bool) { | |||||
| g.needClose = needClose | |||||
| } | |||||
| func (g *GRPCInvoker) RegisterClient(serviceName string, client GRPCClient) { | |||||
| g.clientsMapLock.Lock() | |||||
| defer g.clientsMapLock.Unlock() | |||||
| g.clients[serviceName] = client | |||||
| } | |||||
| func (g *GRPCInvoker) GetClient(serviceName string) GRPCClient { | |||||
| g.clientsMapLock.Lock() | |||||
| defer g.clientsMapLock.Unlock() | |||||
| if client, ok := g.clients[serviceName]; ok { | |||||
| return client | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (g *GRPCInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) { | |||||
| serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl) | |||||
| client := g.GetClient(serviceTaskStateImpl.ServiceName()) | |||||
| if client == nil { | |||||
| return nil, errors.New(fmt.Sprintf("no grpc client %s for service task state", serviceTaskStateImpl.ServiceName())) | |||||
| } | |||||
| // context is the first arg in grpc client method | |||||
| input = append([]any{ctx}, input...) | |||||
| if serviceTaskStateImpl.IsAsync() { | |||||
| go func() { | |||||
| _, err := client.CallMethod(serviceTaskStateImpl, input) | |||||
| if err != nil { | |||||
| log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(), | |||||
| serviceTaskStateImpl.ServiceMethod(), err) | |||||
| } | |||||
| }() | |||||
| return nil, nil | |||||
| } else { | |||||
| return client.CallMethod(serviceTaskStateImpl, input) | |||||
| } | |||||
| } | |||||
| func (g *GRPCInvoker) Close(ctx context.Context) error { | |||||
| if g.needClose { | |||||
| for _, client := range g.clients { | |||||
| err := client.CloseConnection() | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| type GRPCClient interface { | |||||
| CallMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) | |||||
| CloseConnection() error | |||||
| } | |||||
| type GPRCClientImpl struct { | |||||
| serviceName string | |||||
| client any | |||||
| connection *grpc.ClientConn | |||||
| methodLock sync.Mutex | |||||
| } | |||||
| func NewGRPCClient(serviceName string, client any, connection *grpc.ClientConn) *GPRCClientImpl { | |||||
| return &GPRCClientImpl{ | |||||
| serviceName: serviceName, | |||||
| client: client, | |||||
| connection: connection, | |||||
| } | |||||
| } | |||||
| func (g *GPRCClientImpl) CallMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) { | |||||
| if serviceTaskStateImpl.Method() == nil { | |||||
| err := g.initMethod(serviceTaskStateImpl) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| method := serviceTaskStateImpl.Method() | |||||
| args := make([]reflect.Value, 0, len(input)) | |||||
| for _, arg := range input { | |||||
| args = append(args, reflect.ValueOf(arg)) | |||||
| } | |||||
| retryCountMap := make(map[state.Retry]int) | |||||
| for { | |||||
| res, err, shouldRetry := func() (res []reflect.Value, resErr error, shouldRetry bool) { | |||||
| defer func() { | |||||
| // err may happen in the method invoke (panic) and method return, we try to find err and use it to decide retry by | |||||
| // whether contains exception or not | |||||
| if r := recover(); r != nil { | |||||
| errStr := fmt.Sprintf("%v", r) | |||||
| retry := g.matchRetry(serviceTaskStateImpl, errStr) | |||||
| res = nil | |||||
| resErr = errors.New(errStr) | |||||
| if retry == nil { | |||||
| return | |||||
| } | |||||
| shouldRetry = g.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||||
| return | |||||
| } | |||||
| }() | |||||
| outs := method.Call(args) | |||||
| // err is the last arg in grpc client method | |||||
| if err, ok := outs[len(outs)-1].Interface().(error); ok { | |||||
| errStr := err.Error() | |||||
| retry := g.matchRetry(serviceTaskStateImpl, errStr) | |||||
| res = nil | |||||
| resErr = err | |||||
| if retry == nil { | |||||
| return | |||||
| } | |||||
| shouldRetry = g.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||||
| return | |||||
| } | |||||
| // invoke success | |||||
| res = outs | |||||
| resErr = nil | |||||
| shouldRetry = false | |||||
| return | |||||
| }() | |||||
| if !shouldRetry { | |||||
| if err != nil { | |||||
| return nil, errors.New(fmt.Sprintf("invoke Service[%s] failed, not satisfy retry config, the last err is %s", | |||||
| serviceTaskStateImpl.ServiceName(), err)) | |||||
| } | |||||
| return res, nil | |||||
| } | |||||
| } | |||||
| } | |||||
| func (g *GPRCClientImpl) CloseConnection() error { | |||||
| if g.connection != nil { | |||||
| err := g.connection.Close() | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (g *GPRCClientImpl) initMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl) error { | |||||
| methodName := serviceTaskStateImpl.ServiceMethod() | |||||
| g.methodLock.Lock() | |||||
| defer g.methodLock.Unlock() | |||||
| clientValue := reflect.ValueOf(g.client) | |||||
| if clientValue.IsZero() { | |||||
| return errors.New(fmt.Sprintf("invalid client value when grpc client call, serviceName: %s", g.serviceName)) | |||||
| } | |||||
| method := clientValue.MethodByName(methodName) | |||||
| if method.IsZero() { | |||||
| return errors.New(fmt.Sprintf("invalid client method when grpc client call, serviceName: %s, serviceMethod: %s", | |||||
| g.serviceName, methodName)) | |||||
| } | |||||
| serviceTaskStateImpl.SetMethod(&method) | |||||
| return nil | |||||
| } | |||||
| func (g *GPRCClientImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry { | |||||
| if impl.Retry() != nil { | |||||
| for _, retry := range impl.Retry() { | |||||
| if retry.Exceptions() != nil { | |||||
| for _, exception := range retry.Exceptions() { | |||||
| if strings.Contains(str, exception) { | |||||
| return retry | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (g *GPRCClientImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool { | |||||
| attempt, exist := countMap[retry] | |||||
| if !exist { | |||||
| countMap[retry] = 0 | |||||
| } | |||||
| if attempt >= retry.MaxAttempt() { | |||||
| return false | |||||
| } | |||||
| intervalSecond := retry.IntervalSecond() | |||||
| backoffRate := retry.BackoffRate() | |||||
| var currentInterval int64 | |||||
| if attempt == 0 { | |||||
| currentInterval = int64(intervalSecond * 1000) | |||||
| } else { | |||||
| currentInterval = int64(intervalSecond * backoffRate * float64(attempt) * 1000) | |||||
| } | |||||
| log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s", | |||||
| impl.ServiceName(), impl.ServiceMethod(), currentInterval, attempt, err) | |||||
| time.Sleep(time.Duration(currentInterval) * time.Millisecond) | |||||
| countMap[retry] = attempt + 1 | |||||
| return true | |||||
| } | |||||
| @@ -0,0 +1,185 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "google.golang.org/grpc" | |||||
| "google.golang.org/grpc/credentials/insecure" | |||||
| "testing" | |||||
| "time" | |||||
| pb "github.com/seata/seata-go/testdata/saga/engine/invoker/grpc" | |||||
| ) | |||||
| type MockGPRCClientImpl struct { | |||||
| GPRCClientImpl | |||||
| } | |||||
| type mockClientImpl struct { | |||||
| invokeCount int | |||||
| } | |||||
| func (m *mockClientImpl) SayHelloRight(ctx context.Context, word string) (string, error) { | |||||
| m.invokeCount++ | |||||
| fmt.Println("invoke right") | |||||
| return word, nil | |||||
| } | |||||
| func (m *mockClientImpl) SayHelloRightLater(ctx context.Context, word string, delay int) (string, error) { | |||||
| m.invokeCount++ | |||||
| if delay == m.invokeCount { | |||||
| fmt.Println("invoke right") | |||||
| return word, nil | |||||
| } | |||||
| fmt.Println("invoke fail") | |||||
| return "", errors.New("invoke failed") | |||||
| } | |||||
| func TestGRPCInvokerInvokeSucceedWithOutRetry(t *testing.T) { | |||||
| ctx := context.Background() | |||||
| invoker := newGRPCServiceInvoker() | |||||
| values, err := invoker.Invoke(ctx, []any{"hello"}, newHelloServiceTaskState()) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| if values == nil || len(values) == 0 { | |||||
| t.Error("no value in values") | |||||
| return | |||||
| } | |||||
| if values[0].Interface().(string) != "hello" { | |||||
| t.Errorf("expect hello, but got %v", values[0].Interface()) | |||||
| } | |||||
| if _, ok := values[1].Interface().(error); ok { | |||||
| t.Errorf("expect nil, but got %v", values[1].Interface()) | |||||
| } | |||||
| } | |||||
| func TestGRPCInvokerInvokeSucceedInRetry(t *testing.T) { | |||||
| ctx := context.Background() | |||||
| invoker := newGRPCServiceInvoker() | |||||
| values, err := invoker.Invoke(ctx, []any{"hello", 2}, newHelloServiceTaskStateWithRetry()) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| if values == nil || len(values) == 0 { | |||||
| t.Error("no value in values") | |||||
| return | |||||
| } | |||||
| if values[0].Interface().(string) != "hello" { | |||||
| t.Errorf("expect hello, but got %v", values[0].Interface()) | |||||
| } | |||||
| if _, ok := values[1].Interface().(error); ok { | |||||
| t.Errorf("expect nil, but got %v", values[1].Interface()) | |||||
| } | |||||
| } | |||||
| func TestGRPCInvokerInvokeFailedInRetry(t *testing.T) { | |||||
| ctx := context.Background() | |||||
| invoker := newGRPCServiceInvoker() | |||||
| _, err := invoker.Invoke(ctx, []any{"hello", 5}, newHelloServiceTaskStateWithRetry()) | |||||
| if err != nil { | |||||
| assert.Error(t, err) | |||||
| } | |||||
| } | |||||
| func TestGRPCInvokerInvokeE2E(t *testing.T) { | |||||
| go func() { | |||||
| pb.StartProductServer() | |||||
| }() | |||||
| time.Sleep(3000 * time.Millisecond) | |||||
| conn, err := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials())) | |||||
| if err != nil { | |||||
| t.Fatalf("did not connect: %v", err) | |||||
| } | |||||
| c := pb.NewProductInfoClient(conn) | |||||
| grpcClient := NewGRPCClient("product", c, conn) | |||||
| invoker := NewGRPCInvoker() | |||||
| invoker.RegisterClient("product", grpcClient) | |||||
| ctx := context.Background() | |||||
| values, err := invoker.Invoke(ctx, []any{&pb.Product{Id: "123"}}, newProductServiceTaskState()) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| t.Log(values) | |||||
| err = invoker.Close(ctx) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| } | |||||
| func newGRPCServiceInvoker() ServiceInvoker { | |||||
| mockGRPCInvoker := NewGRPCInvoker() | |||||
| mockGRPCClient := &mockClientImpl{} | |||||
| mockClient := NewGRPCClient("hello", mockGRPCClient, &grpc.ClientConn{}) | |||||
| mockGRPCInvoker.RegisterClient("hello", mockClient) | |||||
| return mockGRPCInvoker | |||||
| } | |||||
| func newProductServiceTaskState() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("product") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("product") | |||||
| serviceTaskStateImpl.SetServiceType("GRPC") | |||||
| serviceTaskStateImpl.SetServiceMethod("AddProduct") | |||||
| retryImpl := &state.RetryImpl{} | |||||
| retryImpl.SetExceptions([]string{"fail"}) | |||||
| retryImpl.SetIntervalSecond(1) | |||||
| retryImpl.SetMaxAttempt(3) | |||||
| retryImpl.SetBackoffRate(0.9) | |||||
| serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| func newHelloServiceTaskState() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("hello") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("hello") | |||||
| serviceTaskStateImpl.SetServiceType("GRPC") | |||||
| serviceTaskStateImpl.SetServiceMethod("SayHelloRight") | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| func newHelloServiceTaskStateWithRetry() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("hello") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("hello") | |||||
| serviceTaskStateImpl.SetServiceType("GRPC") | |||||
| serviceTaskStateImpl.SetServiceMethod("SayHelloRightLater") | |||||
| retryImpl := &state.RetryImpl{} | |||||
| retryImpl.SetExceptions([]string{"fail"}) | |||||
| retryImpl.SetIntervalSecond(1) | |||||
| retryImpl.SetMaxAttempt(3) | |||||
| retryImpl.SetBackoffRate(0.9) | |||||
| serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| @@ -0,0 +1,220 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "bytes" | |||||
| "context" | |||||
| "encoding/json" | |||||
| "errors" | |||||
| "fmt" | |||||
| "io" | |||||
| "net/http" | |||||
| "reflect" | |||||
| "strings" | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| const errHttpCode = 400 | |||||
| type HTTPInvoker struct { | |||||
| clientsMapLock sync.Mutex | |||||
| clients map[string]HTTPClient | |||||
| } | |||||
| func NewHTTPInvoker() *HTTPInvoker { | |||||
| return &HTTPInvoker{ | |||||
| clients: make(map[string]HTTPClient), | |||||
| } | |||||
| } | |||||
| func (h *HTTPInvoker) RegisterClient(serviceName string, client HTTPClient) { | |||||
| h.clientsMapLock.Lock() | |||||
| defer h.clientsMapLock.Unlock() | |||||
| h.clients[serviceName] = client | |||||
| } | |||||
| func (h *HTTPInvoker) GetClient(serviceName string) HTTPClient { | |||||
| h.clientsMapLock.Lock() | |||||
| defer h.clientsMapLock.Unlock() | |||||
| return h.clients[serviceName] | |||||
| } | |||||
| func (h *HTTPInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) { | |||||
| serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl) | |||||
| client := h.GetClient(serviceTaskStateImpl.ServiceName()) | |||||
| if client == nil { | |||||
| return nil, fmt.Errorf("no http client %s for service task state", serviceTaskStateImpl.ServiceName()) | |||||
| } | |||||
| if serviceTaskStateImpl.IsAsync() { | |||||
| go func() { | |||||
| _, err := client.Call(ctx, serviceTaskStateImpl, input) | |||||
| if err != nil { | |||||
| log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(), | |||||
| serviceTaskStateImpl.ServiceMethod(), err) | |||||
| } | |||||
| }() | |||||
| return nil, nil | |||||
| } | |||||
| return client.Call(ctx, serviceTaskStateImpl, input) | |||||
| } | |||||
| func (h *HTTPInvoker) Close(ctx context.Context) error { | |||||
| return nil | |||||
| } | |||||
| type HTTPClient interface { | |||||
| Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) | |||||
| } | |||||
| type HTTPClientImpl struct { | |||||
| serviceName string | |||||
| baseURL string | |||||
| client *http.Client | |||||
| } | |||||
| func NewHTTPClient(serviceName string, baseURL string, client *http.Client) *HTTPClientImpl { | |||||
| if client == nil { | |||||
| client = &http.Client{ | |||||
| Timeout: time.Second * 30, | |||||
| } | |||||
| } | |||||
| return &HTTPClientImpl{ | |||||
| serviceName: serviceName, | |||||
| baseURL: baseURL, | |||||
| client: client, | |||||
| } | |||||
| } | |||||
| func (h *HTTPClientImpl) Call(ctx context.Context, serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) { | |||||
| retryCountMap := make(map[state.Retry]int) | |||||
| for { | |||||
| res, err, shouldRetry := func() (res []reflect.Value, resErr error, shouldRetry bool) { | |||||
| defer func() { | |||||
| if r := recover(); r != nil { | |||||
| errStr := fmt.Sprintf("%v", r) | |||||
| retry := h.matchRetry(serviceTaskStateImpl, errStr) | |||||
| resErr = errors.New(errStr) | |||||
| if retry != nil { | |||||
| shouldRetry = h.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||||
| } | |||||
| } | |||||
| }() | |||||
| reqBody, err := json.Marshal(input) | |||||
| if err != nil { | |||||
| return nil, err, false | |||||
| } | |||||
| req, err := http.NewRequestWithContext(ctx, | |||||
| serviceTaskStateImpl.ServiceMethod(), | |||||
| h.baseURL+serviceTaskStateImpl.Name(), | |||||
| bytes.NewBuffer(reqBody)) | |||||
| if err != nil { | |||||
| return nil, err, false | |||||
| } | |||||
| req.Header.Set("Content-Type", "application/json") | |||||
| resp, err := h.client.Do(req) | |||||
| if err != nil { | |||||
| retry := h.matchRetry(serviceTaskStateImpl, err.Error()) | |||||
| if retry != nil { | |||||
| return nil, err, h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err) | |||||
| } | |||||
| return nil, err, false | |||||
| } | |||||
| defer resp.Body.Close() | |||||
| body, err := io.ReadAll(resp.Body) | |||||
| if err != nil { | |||||
| return nil, err, false | |||||
| } | |||||
| if resp.StatusCode >= errHttpCode { | |||||
| errStr := fmt.Sprintf("HTTP error: %d - %s", resp.StatusCode, string(body)) | |||||
| retry := h.matchRetry(serviceTaskStateImpl, errStr) | |||||
| if retry != nil { | |||||
| return nil, errors.New(errStr), h.needRetry(serviceTaskStateImpl, retryCountMap, retry, err) | |||||
| } | |||||
| return nil, errors.New(errStr), false | |||||
| } | |||||
| return []reflect.Value{ | |||||
| reflect.ValueOf(string(body)), | |||||
| reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()), | |||||
| }, nil, false | |||||
| }() | |||||
| if !shouldRetry { | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("invoke Service[%s] failed, not satisfy retry config, the last err is %s", | |||||
| serviceTaskStateImpl.ServiceName(), err) | |||||
| } | |||||
| return res, nil | |||||
| } | |||||
| } | |||||
| } | |||||
| func (h *HTTPClientImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry { | |||||
| if impl.Retry() != nil { | |||||
| for _, retry := range impl.Retry() { | |||||
| if retry.Exceptions() != nil { | |||||
| for _, exception := range retry.Exceptions() { | |||||
| if strings.Contains(str, exception) { | |||||
| return retry | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (h *HTTPClientImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool { | |||||
| attempt, exist := countMap[retry] | |||||
| if !exist { | |||||
| countMap[retry] = 0 | |||||
| } | |||||
| if attempt >= retry.MaxAttempt() { | |||||
| return false | |||||
| } | |||||
| intervalSecond := retry.IntervalSecond() | |||||
| backoffRate := retry.BackoffRate() | |||||
| var currentInterval int64 | |||||
| if attempt == 0 { | |||||
| currentInterval = int64(intervalSecond * 1000) | |||||
| } else { | |||||
| currentInterval = int64(intervalSecond * backoffRate * float64(attempt) * 1000) | |||||
| } | |||||
| log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s", | |||||
| impl.ServiceName(), impl.ServiceMethod(), currentInterval, attempt, err) | |||||
| time.Sleep(time.Duration(currentInterval) * time.Millisecond) | |||||
| countMap[retry] = attempt + 1 | |||||
| return true | |||||
| } | |||||
| @@ -0,0 +1,176 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "encoding/json" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| "time" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func TestHTTPInvokerInvokeSucceedWithOutRetry(t *testing.T) { | |||||
| // create test server | |||||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| var input []interface{} | |||||
| err := json.NewDecoder(r.Body).Decode(&input) | |||||
| if err != nil { | |||||
| http.Error(w, err.Error(), http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write([]byte(input[0].(string))) | |||||
| })) | |||||
| defer server.Close() | |||||
| // create HTTP Invoker | |||||
| invoker := NewHTTPInvoker() | |||||
| client := NewHTTPClient("test", server.URL+"/", &http.Client{}) | |||||
| invoker.RegisterClient("test", client) | |||||
| // invoke | |||||
| ctx := context.Background() | |||||
| values, err := invoker.Invoke(ctx, []any{"hello"}, newHTTPServiceTaskState()) | |||||
| // verify | |||||
| assert.NoError(t, err) | |||||
| assert.NotNil(t, values) | |||||
| assert.Equal(t, "hello", values[0].Interface()) | |||||
| } | |||||
| func TestHTTPInvokerInvokeWithRetry(t *testing.T) { | |||||
| attemptCount := 0 | |||||
| // create test server | |||||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| attemptCount++ | |||||
| if attemptCount < 2 { | |||||
| w.WriteHeader(http.StatusInternalServerError) | |||||
| w.Write([]byte("fail")) | |||||
| return | |||||
| } | |||||
| var input []interface{} | |||||
| json.NewDecoder(r.Body).Decode(&input) | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write([]byte(input[0].(string))) | |||||
| })) | |||||
| defer server.Close() | |||||
| // create HTTP Invoker | |||||
| invoker := NewHTTPInvoker() | |||||
| client := NewHTTPClient("test", server.URL+"/", &http.Client{}) | |||||
| invoker.RegisterClient("test", client) | |||||
| // invoker | |||||
| ctx := context.Background() | |||||
| values, err := invoker.Invoke(ctx, []any{"hello"}, newHTTPServiceTaskStateWithRetry()) | |||||
| // verify | |||||
| assert.NoError(t, err) | |||||
| assert.NotNil(t, values) | |||||
| assert.Equal(t, "hello", values[0].Interface()) | |||||
| assert.Equal(t, 2, attemptCount) | |||||
| } | |||||
| func TestHTTPInvokerInvokeFailedInRetry(t *testing.T) { | |||||
| // create test server | |||||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| w.WriteHeader(http.StatusInternalServerError) | |||||
| w.Write([]byte("fail")) | |||||
| })) | |||||
| defer server.Close() | |||||
| // create HTTP Invoker | |||||
| invoker := NewHTTPInvoker() | |||||
| client := NewHTTPClient("test", server.URL+"/", &http.Client{}) | |||||
| invoker.RegisterClient("test", client) | |||||
| // invoker | |||||
| ctx := context.Background() | |||||
| _, err := invoker.Invoke(ctx, []any{"hello"}, newHTTPServiceTaskStateWithRetry()) | |||||
| // verify | |||||
| assert.Error(t, err) | |||||
| } | |||||
| func TestHTTPInvokerAsyncInvoke(t *testing.T) { | |||||
| called := false | |||||
| // create test server | |||||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| called = true | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write([]byte("success")) | |||||
| })) | |||||
| defer server.Close() | |||||
| // create HTTP Invoker | |||||
| invoker := NewHTTPInvoker() | |||||
| client := NewHTTPClient("test", server.URL+"/", &http.Client{}) | |||||
| invoker.RegisterClient("test", client) | |||||
| // async invoke | |||||
| ctx := context.Background() | |||||
| taskState := newHTTPServiceTaskStateWithAsync() | |||||
| _, err := invoker.Invoke(ctx, []any{"hello"}, taskState) | |||||
| // verify | |||||
| assert.NoError(t, err) | |||||
| time.Sleep(100 * time.Millisecond) | |||||
| assert.True(t, called) | |||||
| } | |||||
| func newHTTPServiceTaskState() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("test") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("test") | |||||
| serviceTaskStateImpl.SetServiceType("HTTP") | |||||
| serviceTaskStateImpl.SetServiceMethod("POST") | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| func newHTTPServiceTaskStateWithAsync() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("test") | |||||
| serviceTaskStateImpl.SetIsAsync(true) | |||||
| serviceTaskStateImpl.SetServiceName("test") | |||||
| serviceTaskStateImpl.SetServiceType("HTTP") | |||||
| serviceTaskStateImpl.SetServiceMethod("POST") | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| func newHTTPServiceTaskStateWithRetry() state.ServiceTaskState { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| serviceTaskStateImpl.SetName("test") | |||||
| serviceTaskStateImpl.SetIsAsync(false) | |||||
| serviceTaskStateImpl.SetServiceName("test") | |||||
| serviceTaskStateImpl.SetServiceType("HTTP") | |||||
| serviceTaskStateImpl.SetServiceMethod("POST") | |||||
| retryImpl := &state.RetryImpl{} | |||||
| retryImpl.SetExceptions([]string{"fail"}) | |||||
| retryImpl.SetIntervalSecond(1) | |||||
| retryImpl.SetMaxAttempt(3) | |||||
| retryImpl.SetBackoffRate(0.9) | |||||
| serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||||
| return serviceTaskStateImpl | |||||
| } | |||||
| @@ -0,0 +1,62 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package invoker | |||||
| import ( | |||||
| "context" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| "reflect" | |||||
| "sync" | |||||
| ) | |||||
| type ScriptInvokerManager interface { | |||||
| } | |||||
| type ScriptInvoker interface { | |||||
| } | |||||
| type ServiceInvokerManager interface { | |||||
| ServiceInvoker(serviceType string) ServiceInvoker | |||||
| PutServiceInvoker(serviceType string, invoker ServiceInvoker) | |||||
| } | |||||
| type ServiceInvoker interface { | |||||
| Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) | |||||
| Close(ctx context.Context) error | |||||
| } | |||||
| type ServiceInvokerManagerImpl struct { | |||||
| invokers map[string]ServiceInvoker | |||||
| mutex sync.Mutex | |||||
| } | |||||
| func NewServiceInvokerManagerImpl() *ServiceInvokerManagerImpl { | |||||
| return &ServiceInvokerManagerImpl{ | |||||
| invokers: make(map[string]ServiceInvoker), | |||||
| } | |||||
| } | |||||
| func (manager *ServiceInvokerManagerImpl) ServiceInvoker(serviceType string) ServiceInvoker { | |||||
| return manager.invokers[serviceType] | |||||
| } | |||||
| func (manager *ServiceInvokerManagerImpl) PutServiceInvoker(serviceType string, invoker ServiceInvoker) { | |||||
| manager.mutex.Lock() | |||||
| defer manager.mutex.Unlock() | |||||
| manager.invokers[serviceType] = invoker | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package sequence | |||||
| type SeqGenerator interface { | |||||
| GenerateId(entity string, ruleName string) string | |||||
| } | |||||
| @@ -0,0 +1,133 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package sequence | |||||
| import ( | |||||
| "fmt" | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| // SnowflakeSeqGenerator snowflake gen ids | |||||
| // ref: https://en.wikipedia.org/wiki/Snowflake_ID | |||||
| var ( | |||||
| // set the beginning time | |||||
| epoch = time.Date(2024, time.January, 01, 00, 00, 00, 00, time.UTC).UnixMilli() | |||||
| ) | |||||
| const ( | |||||
| // timestamp occupancy bits | |||||
| timestampBits = 41 | |||||
| // dataCenterId occupancy bits | |||||
| dataCenterIdBits = 5 | |||||
| // workerId occupancy bits | |||||
| workerIdBits = 5 | |||||
| // sequence occupancy bits | |||||
| seqBits = 12 | |||||
| // timestamp max value, just like 2^41-1 = 2199023255551 | |||||
| timestampMaxValue = -1 ^ (-1 << timestampBits) | |||||
| // dataCenterId max value, just like 2^5-1 = 31 | |||||
| dataCenterIdMaxValue = -1 ^ (-1 << dataCenterIdBits) | |||||
| // workId max value, just like 2^5-1 = 31 | |||||
| workerIdMaxValue = -1 ^ (-1 << workerIdBits) | |||||
| // sequence max value, just like 2^12-1 = 4095 | |||||
| seqMaxValue = -1 ^ (-1 << seqBits) | |||||
| // number of workId offsets (seqBits) | |||||
| workIdShift = 12 | |||||
| // number of dataCenterId offsets (seqBits + workerIdBits) | |||||
| dataCenterIdShift = 17 | |||||
| // number of timestamp offsets (seqBits + workerIdBits + dataCenterIdBits) | |||||
| timestampShift = 22 | |||||
| defaultInitValue = 0 | |||||
| ) | |||||
| type SnowflakeSeqGenerator struct { | |||||
| mu *sync.Mutex | |||||
| timestamp int64 | |||||
| dataCenterId int64 | |||||
| workerId int64 | |||||
| sequence int64 | |||||
| } | |||||
| // NewSnowflakeSeqGenerator initiates the snowflake generator | |||||
| func NewSnowflakeSeqGenerator(dataCenterId, workId int64) (r *SnowflakeSeqGenerator, err error) { | |||||
| if dataCenterId < 0 || dataCenterId > dataCenterIdMaxValue { | |||||
| err = fmt.Errorf("dataCenterId should between 0 and %d", dataCenterIdMaxValue-1) | |||||
| return | |||||
| } | |||||
| if workId < 0 || workId > workerIdMaxValue { | |||||
| err = fmt.Errorf("workId should between 0 and %d", dataCenterIdMaxValue-1) | |||||
| return | |||||
| } | |||||
| return &SnowflakeSeqGenerator{ | |||||
| mu: new(sync.Mutex), | |||||
| timestamp: defaultInitValue - 1, | |||||
| dataCenterId: dataCenterId, | |||||
| workerId: workId, | |||||
| sequence: defaultInitValue, | |||||
| }, nil | |||||
| } | |||||
| // GenerateId timestamp + dataCenterId + workId + sequence | |||||
| func (S *SnowflakeSeqGenerator) GenerateId(entity string, ruleName string) string { | |||||
| S.mu.Lock() | |||||
| defer S.mu.Unlock() | |||||
| now := time.Now().UnixMilli() | |||||
| if S.timestamp > now { // Clock callback | |||||
| log.Errorf("Clock moved backwards. Refusing to generate ID, last timestamp is %d, now is %d", S.timestamp, now) | |||||
| return "" | |||||
| } | |||||
| if S.timestamp == now { | |||||
| // generate multiple IDs in the same millisecond, incrementing the sequence number to prevent conflicts | |||||
| S.sequence = (S.sequence + 1) & seqMaxValue | |||||
| if S.sequence == 0 { | |||||
| // sequence overflow, waiting for next millisecond | |||||
| for now <= S.timestamp { | |||||
| now = time.Now().UnixMilli() | |||||
| } | |||||
| } | |||||
| } else { | |||||
| // initialized sequences are used directly at different millisecond timestamps | |||||
| S.sequence = defaultInitValue | |||||
| } | |||||
| tmp := now - epoch | |||||
| if tmp > timestampMaxValue { | |||||
| log.Errorf("epoch should between 0 and %d", timestampMaxValue-1) | |||||
| return "" | |||||
| } | |||||
| S.timestamp = now | |||||
| // combine the parts to generate the final ID and convert the 64-bit binary to decimal digits. | |||||
| r := (tmp)<<timestampShift | | |||||
| (S.dataCenterId << dataCenterIdShift) | | |||||
| (S.workerId << workIdShift) | | |||||
| (S.sequence) | |||||
| return fmt.Sprintf("%d", r) | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package sequence | |||||
| import ( | |||||
| "strconv" | |||||
| "testing" | |||||
| ) | |||||
| func TestSnowflakeSeqGenerator_GenerateId(t *testing.T) { | |||||
| var dataCenterId, workId int64 = 1, 1 | |||||
| generator, err := NewSnowflakeSeqGenerator(dataCenterId, workId) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| var x, y string | |||||
| for i := 0; i < 100; i++ { | |||||
| y = generator.GenerateId("", "") | |||||
| if x == y { | |||||
| t.Errorf("x(%s) & y(%s) are the same", x, y) | |||||
| } | |||||
| x = y | |||||
| } | |||||
| } | |||||
| func TestEpoch(t *testing.T) { | |||||
| t.Log(epoch) | |||||
| t.Log(len(strconv.FormatInt(epoch, 10))) | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package sequence | |||||
| import "github.com/google/uuid" | |||||
| type UUIDSeqGenerator struct { | |||||
| } | |||||
| func NewUUIDSeqGenerator() *UUIDSeqGenerator { | |||||
| return &UUIDSeqGenerator{} | |||||
| } | |||||
| func (U UUIDSeqGenerator) GenerateId(entity string, ruleName string) string { | |||||
| return uuid.New().String() | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package serializer | |||||
| import ( | |||||
| "bytes" | |||||
| "encoding/gob" | |||||
| "encoding/json" | |||||
| "github.com/pkg/errors" | |||||
| ) | |||||
| type ParamsSerializer struct{} | |||||
| func (ParamsSerializer) Serialize(object any) (string, error) { | |||||
| result, err := json.Marshal(object) | |||||
| return string(result), err | |||||
| } | |||||
| func (ParamsSerializer) Deserialize(object string) (any, error) { | |||||
| var result any | |||||
| err := json.Unmarshal([]byte(object), &result) | |||||
| return result, err | |||||
| } | |||||
| type ErrorSerializer struct{} | |||||
| func (ErrorSerializer) Serialize(object error) ([]byte, error) { | |||||
| var buffer bytes.Buffer | |||||
| encoder := gob.NewEncoder(&buffer) | |||||
| if object != nil { | |||||
| err := encoder.Encode(object.Error()) | |||||
| return buffer.Bytes(), err | |||||
| } | |||||
| return nil, nil | |||||
| } | |||||
| func (ErrorSerializer) Deserialize(object []byte) (error, error) { | |||||
| var errorMsg string | |||||
| buffer := bytes.NewReader(object) | |||||
| encoder := gob.NewDecoder(buffer) | |||||
| err := encoder.Decode(&errorMsg) | |||||
| return errors.New(errorMsg), err | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package serializer | |||||
| import ( | |||||
| "github.com/pkg/errors" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "testing" | |||||
| ) | |||||
| func TestErrorSerializer(t *testing.T) { | |||||
| serializer := ErrorSerializer{} | |||||
| expected := errors.New("This is a test error") | |||||
| serialized, err := serializer.Serialize(expected) | |||||
| assert.Nil(t, err) | |||||
| actual, err := serializer.Deserialize(serialized) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, expected.Error(), actual.Error()) | |||||
| } | |||||
| @@ -0,0 +1,191 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package handlers | |||||
| import ( | |||||
| "context" | |||||
| "errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| type ServiceTaskStateHandler struct { | |||||
| interceptors []core.StateHandlerInterceptor | |||||
| } | |||||
| func NewServiceTaskStateHandler() *ServiceTaskStateHandler { | |||||
| return &ServiceTaskStateHandler{} | |||||
| } | |||||
| func (s *ServiceTaskStateHandler) State() string { | |||||
| return constant.StateTypeServiceTask | |||||
| } | |||||
| func (s *ServiceTaskStateHandler) Process(ctx context.Context, processContext core.ProcessContext) error { | |||||
| stateInstruction, ok := processContext.GetInstruction().(core.StateInstruction) | |||||
| if !ok { | |||||
| return errors.New("invalid state instruction from processContext") | |||||
| } | |||||
| stateInterface, err := stateInstruction.GetState(processContext) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| serviceTaskStateImpl, ok := stateInterface.(*state.ServiceTaskStateImpl) | |||||
| serviceName := serviceTaskStateImpl.ServiceName() | |||||
| methodName := serviceTaskStateImpl.ServiceMethod() | |||||
| stateInstance, ok := processContext.GetVariable(constant.VarNameStateInst).(statelang.StateInstance) | |||||
| if !ok { | |||||
| return errors.New("invalid state instance type from processContext") | |||||
| } | |||||
| // invoke service task and record | |||||
| var result any | |||||
| var resultErr error | |||||
| handleResultErr := func(err error) { | |||||
| log.Error("<<<<<<<<<<<<<<<<<<<<<< State[%s], ServiceName[%s], Method[%s] Execute failed.", | |||||
| serviceTaskStateImpl.Name(), serviceName, methodName, err) | |||||
| hierarchicalProcessContext, ok := processContext.(core.HierarchicalProcessContext) | |||||
| if !ok { | |||||
| return | |||||
| } | |||||
| hierarchicalProcessContext.SetVariable(constant.VarNameCurrentException, err) | |||||
| core.HandleException(processContext, serviceTaskStateImpl.AbstractTaskState, err) | |||||
| } | |||||
| input, ok := processContext.GetVariable(constant.VarNameInputParams).([]any) | |||||
| if !ok { | |||||
| handleResultErr(errors.New("invalid input params type from processContext")) | |||||
| return nil | |||||
| } | |||||
| stateInstance.SetStatus(statelang.RU) | |||||
| log.Debugf(">>>>>>>>>>>>>>>>>>>>>> Start to execute State[%s], ServiceName[%s], Method[%s], Input:%s", | |||||
| serviceTaskStateImpl.Name(), serviceName, methodName, input) | |||||
| if _, ok := stateInterface.(state.CompensateSubStateMachineState); ok { | |||||
| // If it is the compensation of the subState machine, | |||||
| // directly call the state machine's compensate method | |||||
| stateMachineEngine, ok := processContext.GetVariable(constant.VarNameStateMachineEngine).(core.StateMachineEngine) | |||||
| if !ok { | |||||
| handleResultErr(errors.New("invalid stateMachineEngine type from processContext")) | |||||
| return nil | |||||
| } | |||||
| result, resultErr = s.compensateSubStateMachine(ctx, processContext, serviceTaskStateImpl, input, | |||||
| stateInstance, stateMachineEngine) | |||||
| if resultErr != nil { | |||||
| handleResultErr(resultErr) | |||||
| return nil | |||||
| } | |||||
| } else { | |||||
| stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(core.StateMachineConfig) | |||||
| if !ok { | |||||
| handleResultErr(errors.New("invalid stateMachineConfig type from processContext")) | |||||
| return nil | |||||
| } | |||||
| serviceInvoker := stateMachineConfig.ServiceInvokerManager().ServiceInvoker(serviceTaskStateImpl.ServiceType()) | |||||
| if serviceInvoker == nil { | |||||
| resultErr = exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "No such ServiceInvoker["+serviceTaskStateImpl.ServiceType()+"]", nil) | |||||
| handleResultErr(resultErr) | |||||
| return nil | |||||
| } | |||||
| result, resultErr = serviceInvoker.Invoke(ctx, input, serviceTaskStateImpl) | |||||
| if resultErr != nil { | |||||
| handleResultErr(resultErr) | |||||
| return nil | |||||
| } | |||||
| } | |||||
| log.Debugf("<<<<<<<<<<<<<<<<<<<<<< State[%s], ServiceName[%s], Method[%s] Execute finish. result: %s", | |||||
| serviceTaskStateImpl.Name(), serviceName, methodName, result) | |||||
| if result != nil { | |||||
| stateInstance.SetOutputParams(result) | |||||
| hierarchicalProcessContext, ok := processContext.(core.HierarchicalProcessContext) | |||||
| if !ok { | |||||
| handleResultErr(errors.New("invalid hierarchical process context type from processContext")) | |||||
| return nil | |||||
| } | |||||
| hierarchicalProcessContext.SetVariable(constant.VarNameOutputParams, result) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (s *ServiceTaskStateHandler) StateHandlerInterceptorList() []core.StateHandlerInterceptor { | |||||
| return s.interceptors | |||||
| } | |||||
| func (s *ServiceTaskStateHandler) RegistryStateHandlerInterceptor(stateHandlerInterceptor core.StateHandlerInterceptor) { | |||||
| s.interceptors = append(s.interceptors, stateHandlerInterceptor) | |||||
| } | |||||
| func (s *ServiceTaskStateHandler) compensateSubStateMachine(ctx context.Context, processContext core.ProcessContext, | |||||
| serviceTaskState state.ServiceTaskState, input any, instance statelang.StateInstance, | |||||
| machineEngine core.StateMachineEngine) (any, error) { | |||||
| subStateMachineParentId, ok := processContext.GetVariable(serviceTaskState.Name() + constant.VarNameSubMachineParentId).(string) | |||||
| if !ok { | |||||
| return nil, errors.New("invalid subStateMachineParentId type from processContext") | |||||
| } | |||||
| if subStateMachineParentId == "" { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "sub statemachine parentId is required", nil) | |||||
| } | |||||
| stateMachineConfig := processContext.GetVariable(constant.VarNameStateMachineConfig).(core.StateMachineConfig) | |||||
| subInst, err := stateMachineConfig.StateLogStore().GetStateMachineInstanceByParentId(subStateMachineParentId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if subInst == nil || len(subInst) == 0 { | |||||
| return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||||
| "cannot find sub statemachine instance by parentId:"+subStateMachineParentId, nil) | |||||
| } | |||||
| subStateMachineInstId := subInst[0].ID() | |||||
| log.Debugf(">>>>>>>>>>>>>>>>>>>>>> Start to compensate sub statemachine [id:%s]", subStateMachineInstId) | |||||
| startParams := make(map[string]any) | |||||
| if inputList, ok := input.([]any); ok { | |||||
| if len(inputList) > 0 { | |||||
| startParams = inputList[0].(map[string]any) | |||||
| } | |||||
| } else if inputMap, ok := input.(map[string]any); ok { | |||||
| startParams = inputMap | |||||
| } | |||||
| compensateInst, err := machineEngine.Compensate(ctx, subStateMachineInstId, startParams) | |||||
| instance.SetStatus(compensateInst.CompensationStatus()) | |||||
| log.Debugf("<<<<<<<<<<<<<<<<<<<<<< Compensate sub statemachine [id:%s] finished with status[%s], "+"compensateState[%s]", | |||||
| subStateMachineInstId, compensateInst.Status(), compensateInst.CompensationStatus()) | |||||
| return compensateInst.EndParams(), nil | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package process | |||||
| type ProcessType string | |||||
| const ( | |||||
| StateLang ProcessType = "STATE_LANG" // SEATA State Language | |||||
| ) | |||||
| @@ -0,0 +1,91 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type ChoiceStateParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewChoiceStateParser() *ChoiceStateParser { | |||||
| return &ChoiceStateParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (c ChoiceStateParser) StateType() string { | |||||
| return constant.StateTypeChoice | |||||
| } | |||||
| func (c ChoiceStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| choiceState := state.NewChoiceStateImpl() | |||||
| choiceState.SetName(stateName) | |||||
| //parse Type | |||||
| typeName, err := c.GetString(stateName, stateMap, "Type") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| choiceState.SetType(typeName) | |||||
| //parse Default | |||||
| defaultChoice, err := c.GetString(stateName, stateMap, "Default") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| choiceState.SetDefault(defaultChoice) | |||||
| //parse Choices | |||||
| slice, err := c.GetSlice(stateName, stateMap, "Choices") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| var choices []state.Choice | |||||
| for i := range slice { | |||||
| choiceValMap, ok := slice[i].(map[string]interface{}) | |||||
| if !ok { | |||||
| return nil, errors.New(fmt.Sprintf("State [%s] Choices element required struct", stateName)) | |||||
| } | |||||
| choice := state.NewChoiceImpl() | |||||
| expression, err := c.GetString(stateName, choiceValMap, "Expression") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| choice.SetExpression(expression) | |||||
| next, err := c.GetString(stateName, choiceValMap, "Next") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| choice.SetNext(next) | |||||
| choices = append(choices, choice) | |||||
| } | |||||
| choiceState.SetChoices(choices) | |||||
| return choiceState, nil | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type CompensationTriggerStateParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewCompensationTriggerStateParser() *CompensationTriggerStateParser { | |||||
| return &CompensationTriggerStateParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (c CompensationTriggerStateParser) StateType() string { | |||||
| return constant.StateTypeCompensationTrigger | |||||
| } | |||||
| func (c CompensationTriggerStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| compensateSubStateMachineStateImpl := state.NewCompensationTriggerStateImpl() | |||||
| err := c.ParseBaseAttributes(stateName, compensateSubStateMachineStateImpl, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return compensateSubStateMachineStateImpl, nil | |||||
| } | |||||
| @@ -0,0 +1,83 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type SucceedEndStateParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewSucceedEndStateParser() *SucceedEndStateParser { | |||||
| return &SucceedEndStateParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (s SucceedEndStateParser) StateType() string { | |||||
| return constant.StateTypeSucceed | |||||
| } | |||||
| func (s SucceedEndStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| succeedEndStateImpl := state.NewSucceedEndStateImpl() | |||||
| err := s.ParseBaseAttributes(stateName, succeedEndStateImpl, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return succeedEndStateImpl, nil | |||||
| } | |||||
| type FailEndStateParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewFailEndStateParser() *FailEndStateParser { | |||||
| return &FailEndStateParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (f FailEndStateParser) StateType() string { | |||||
| return constant.StateTypeFail | |||||
| } | |||||
| func (f FailEndStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| failEndStateImpl := state.NewFailEndStateImpl() | |||||
| err := f.ParseBaseAttributes(stateName, failEndStateImpl, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| errorCode, err := f.GetStringOrDefault(stateName, stateMap, "ErrorCode", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| failEndStateImpl.SetErrorCode(errorCode) | |||||
| message, err := f.GetStringOrDefault(stateName, stateMap, "Message", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| failEndStateImpl.SetMessage(message) | |||||
| return failEndStateImpl, nil | |||||
| } | |||||
| @@ -0,0 +1,139 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine" | |||||
| "io" | |||||
| "os" | |||||
| "github.com/knadh/koanf" | |||||
| "github.com/knadh/koanf/parsers/json" | |||||
| "github.com/knadh/koanf/parsers/yaml" | |||||
| "github.com/knadh/koanf/providers/rawbytes" | |||||
| ) | |||||
| // ConfigParser is a general configuration parser interface, used to agree on the implementation of different types of parsers | |||||
| type ConfigParser interface { | |||||
| Parse(configContent []byte) (*statemachine.StateMachineObject, error) | |||||
| } | |||||
| type JSONConfigParser struct{} | |||||
| func NewJSONConfigParser() *JSONConfigParser { | |||||
| return &JSONConfigParser{} | |||||
| } | |||||
| func (p *JSONConfigParser) Parse(configContent []byte) (*statemachine.StateMachineObject, error) { | |||||
| if configContent == nil || len(configContent) == 0 { | |||||
| return nil, fmt.Errorf("empty JSON config content") | |||||
| } | |||||
| k := koanf.New(".") | |||||
| if err := k.Load(rawbytes.Provider(configContent), json.Parser()); err != nil { | |||||
| return nil, fmt.Errorf("failed to parse JSON config content: %w", err) | |||||
| } | |||||
| var stateMachineObject statemachine.StateMachineObject | |||||
| if err := k.Unmarshal("", &stateMachineObject); err != nil { | |||||
| return nil, fmt.Errorf("failed to unmarshal JSON config to struct: %w", err) | |||||
| } | |||||
| return &stateMachineObject, nil | |||||
| } | |||||
| type YAMLConfigParser struct{} | |||||
| func NewYAMLConfigParser() *YAMLConfigParser { | |||||
| return &YAMLConfigParser{} | |||||
| } | |||||
| func (p *YAMLConfigParser) Parse(configContent []byte) (*statemachine.StateMachineObject, error) { | |||||
| if configContent == nil || len(configContent) == 0 { | |||||
| return nil, fmt.Errorf("empty YAML config content") | |||||
| } | |||||
| k := koanf.New(".") | |||||
| if err := k.Load(rawbytes.Provider(configContent), yaml.Parser()); err != nil { | |||||
| return nil, fmt.Errorf("failed to parse YAML config content: %w", err) | |||||
| } | |||||
| var stateMachineObject statemachine.StateMachineObject | |||||
| if err := k.Unmarshal("", &stateMachineObject); err != nil { | |||||
| return nil, fmt.Errorf("failed to unmarshal YAML config to struct: %w", err) | |||||
| } | |||||
| return &stateMachineObject, nil | |||||
| } | |||||
| type StateMachineConfigParser struct{} | |||||
| func NewStateMachineConfigParser() *StateMachineConfigParser { | |||||
| return &StateMachineConfigParser{} | |||||
| } | |||||
| func (p *StateMachineConfigParser) CheckConfigFile(filePath string) error { | |||||
| _, err := os.Stat(filePath) | |||||
| if os.IsNotExist(err) { | |||||
| return fmt.Errorf("config file %s does not exist: %w", filePath, err) | |||||
| } | |||||
| if err != nil { | |||||
| return fmt.Errorf("failed to access config file %s: %w", filePath, err) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (p *StateMachineConfigParser) ReadConfigFile(configFilePath string) ([]byte, error) { | |||||
| file, _ := os.Open(configFilePath) | |||||
| defer func(file *os.File) { | |||||
| _ = file.Close() | |||||
| }(file) | |||||
| var buf bytes.Buffer | |||||
| _, err := io.Copy(&buf, file) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("failed to read config file %s: %w", configFilePath, err) | |||||
| } | |||||
| return buf.Bytes(), nil | |||||
| } | |||||
| func (p *StateMachineConfigParser) getParser(content []byte) (ConfigParser, error) { | |||||
| k := koanf.New(".") | |||||
| if err := k.Load(rawbytes.Provider(content), json.Parser()); err == nil { | |||||
| return NewJSONConfigParser(), nil | |||||
| } | |||||
| k = koanf.New(".") | |||||
| if err := k.Load(rawbytes.Provider(content), yaml.Parser()); err == nil { | |||||
| return NewYAMLConfigParser(), nil | |||||
| } | |||||
| return nil, fmt.Errorf("unsupported config file format") | |||||
| } | |||||
| func (p *StateMachineConfigParser) Parse(content []byte) (*statemachine.StateMachineObject, error) { | |||||
| parser, err := p.getParser(content) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return parser.Parse(content) | |||||
| } | |||||
| @@ -0,0 +1,882 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "testing" | |||||
| ) | |||||
| func TestStateMachineConfigParser_Parse(t *testing.T) { | |||||
| parser := NewStateMachineConfigParser() | |||||
| tests := []struct { | |||||
| name string | |||||
| configFilePath string | |||||
| expectedObject *statemachine.StateMachineObject | |||||
| }{ | |||||
| { | |||||
| name: "JSON Simple 1", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statelang_with_choice.json", | |||||
| expectedObject: GetStateMachineObject1("json"), | |||||
| }, | |||||
| { | |||||
| name: "JSON Simple 2", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statemachine.json", | |||||
| expectedObject: GetStateMachineObject2("json"), | |||||
| }, | |||||
| { | |||||
| name: "JSON Simple 3", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/state_machine_new_designer.json", | |||||
| expectedObject: GetStateMachineObject3("json"), | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple 1", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statelang_with_choice.yaml", | |||||
| expectedObject: GetStateMachineObject1("yaml"), | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple 2", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statemachine.yaml", | |||||
| expectedObject: GetStateMachineObject2("yaml"), | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple 3", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/state_machine_new_designer.yaml", | |||||
| expectedObject: GetStateMachineObject3("yaml"), | |||||
| }, | |||||
| } | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| content, err := parser.ReadConfigFile(tt.configFilePath) | |||||
| if err != nil { | |||||
| t.Error("parse fail: " + err.Error()) | |||||
| } | |||||
| object, err := parser.Parse(content) | |||||
| if err != nil { | |||||
| t.Error("parse fail: " + err.Error()) | |||||
| } | |||||
| assert.Equal(t, tt.expectedObject, object) | |||||
| }) | |||||
| } | |||||
| } | |||||
| func GetStateMachineObject1(format string) *statemachine.StateMachineObject { | |||||
| switch format { | |||||
| case "json": | |||||
| case "yaml": | |||||
| } | |||||
| return &statemachine.StateMachineObject{ | |||||
| Name: "simpleChoiceTestStateMachine", | |||||
| Comment: "带条件分支的测试状态机定义", | |||||
| StartState: "FirstState", | |||||
| Version: "0.0.1", | |||||
| States: map[string]interface{}{ | |||||
| "FirstState": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "foo", | |||||
| "Next": "ChoiceState", | |||||
| }, | |||||
| "ChoiceState": map[string]interface{}{ | |||||
| "Type": "Choice", | |||||
| "Choices": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "Expression": "[a] == 1", | |||||
| "Next": "SecondState", | |||||
| }, | |||||
| map[string]interface{}{ | |||||
| "Expression": "[a] == 2", | |||||
| "Next": "ThirdState", | |||||
| }, | |||||
| }, | |||||
| "Default": "SecondState", | |||||
| }, | |||||
| "SecondState": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "bar", | |||||
| }, | |||||
| "ThirdState": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "foo", | |||||
| }, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func GetStateMachineObject2(format string) *statemachine.StateMachineObject { | |||||
| var retryMap map[string]interface{} | |||||
| switch format { | |||||
| case "json": | |||||
| retryMap = map[string]interface{}{ | |||||
| "Exceptions": []interface{}{ | |||||
| "java.lang.Exception", | |||||
| }, | |||||
| "IntervalSeconds": float64(2), | |||||
| "MaxAttempts": float64(3), | |||||
| "BackoffRate": 1.5, | |||||
| } | |||||
| case "yaml": | |||||
| retryMap = map[string]interface{}{ | |||||
| "Exceptions": []interface{}{ | |||||
| "java.lang.Exception", | |||||
| }, | |||||
| "IntervalSeconds": 2, | |||||
| "MaxAttempts": 3, | |||||
| "BackoffRate": 1.5, | |||||
| } | |||||
| } | |||||
| return &statemachine.StateMachineObject{ | |||||
| Name: "simpleTestStateMachine", | |||||
| Comment: "测试状态机定义", | |||||
| StartState: "FirstState", | |||||
| Version: "0.0.1", | |||||
| States: map[string]interface{}{ | |||||
| "FirstState": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "is.seata.saga.DemoService", | |||||
| "ServiceMethod": "foo", | |||||
| "IsPersist": false, | |||||
| "Next": "ScriptState", | |||||
| }, | |||||
| "ScriptState": map[string]interface{}{ | |||||
| "Type": "ScriptTask", | |||||
| "ScriptType": "groovy", | |||||
| "ScriptContent": "return 'hello ' + inputA", | |||||
| "Input": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "inputA": "$.data1", | |||||
| }, | |||||
| }, | |||||
| "Output": map[string]interface{}{ | |||||
| "scriptStateResult": "$.#root", | |||||
| }, | |||||
| "Next": "ChoiceState", | |||||
| }, | |||||
| "ChoiceState": map[string]interface{}{ | |||||
| "Type": "Choice", | |||||
| "Choices": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "Expression": "foo == 1", | |||||
| "Next": "FirstMatchState", | |||||
| }, | |||||
| map[string]interface{}{ | |||||
| "Expression": "foo == 2", | |||||
| "Next": "SecondMatchState", | |||||
| }, | |||||
| }, | |||||
| "Default": "FailState", | |||||
| }, | |||||
| "FirstMatchState": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "is.seata.saga.DemoService", | |||||
| "ServiceMethod": "bar", | |||||
| "CompensateState": "CompensateFirst", | |||||
| "Status": map[string]interface{}{ | |||||
| "return.code == 'S'": "SU", | |||||
| "return.code == 'F'": "FA", | |||||
| "$exception{java.lang.Throwable}": "UN", | |||||
| }, | |||||
| "Input": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "inputA1": "$.data1", | |||||
| "inputA2": map[string]interface{}{ | |||||
| "a": "$.data2.a", | |||||
| }, | |||||
| }, | |||||
| map[string]interface{}{ | |||||
| "inputB": "$.header", | |||||
| }, | |||||
| }, | |||||
| "Output": map[string]interface{}{ | |||||
| "firstMatchStateResult": "$.#root", | |||||
| }, | |||||
| "Retry": []interface{}{ | |||||
| retryMap, | |||||
| }, | |||||
| "Catch": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "Exceptions": []interface{}{ | |||||
| "java.lang.Exception", | |||||
| }, | |||||
| "Next": "CompensationTrigger", | |||||
| }, | |||||
| }, | |||||
| "Next": "SuccessState", | |||||
| }, | |||||
| "CompensateFirst": map[string]interface{}{ | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "is.seata.saga.DemoService", | |||||
| "ServiceMethod": "compensateBar", | |||||
| "IsForCompensation": true, | |||||
| "IsForUpdate": true, | |||||
| "Input": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "input": "$.data", | |||||
| }, | |||||
| }, | |||||
| "Output": map[string]interface{}{ | |||||
| "firstMatchStateResult": "$.#root", | |||||
| }, | |||||
| "Status": map[string]interface{}{ | |||||
| "return.code == 'S'": "SU", | |||||
| "return.code == 'F'": "FA", | |||||
| "$exception{java.lang.Throwable}": "UN", | |||||
| }, | |||||
| }, | |||||
| "CompensationTrigger": map[string]interface{}{ | |||||
| "Type": "CompensationTrigger", | |||||
| "Next": "CompensateEndState", | |||||
| }, | |||||
| "CompensateEndState": map[string]interface{}{ | |||||
| "Type": "Fail", | |||||
| "ErrorCode": "StateCompensated", | |||||
| "Message": "State Compensated!", | |||||
| }, | |||||
| "SecondMatchState": map[string]interface{}{ | |||||
| "Type": "SubStateMachine", | |||||
| "StateMachineName": "simpleTestSubStateMachine", | |||||
| "Input": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "input": "$.data", | |||||
| }, | |||||
| map[string]interface{}{ | |||||
| "header": "$.header", | |||||
| }, | |||||
| }, | |||||
| "Output": map[string]interface{}{ | |||||
| "firstMatchStateResult": "$.#root", | |||||
| }, | |||||
| "Next": "SuccessState", | |||||
| }, | |||||
| "FailState": map[string]interface{}{ | |||||
| "Type": "Fail", | |||||
| "ErrorCode": "DefaultStateError", | |||||
| "Message": "No Matches!", | |||||
| }, | |||||
| "SuccessState": map[string]interface{}{ | |||||
| "Type": "Succeed", | |||||
| }, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func GetStateMachineObject3(format string) *statemachine.StateMachineObject { | |||||
| var ( | |||||
| boundsMap1 map[string]interface{} | |||||
| boundsMap2 map[string]interface{} | |||||
| boundsMap3 map[string]interface{} | |||||
| boundsMap4 map[string]interface{} | |||||
| boundsMap5 map[string]interface{} | |||||
| boundsMap6 map[string]interface{} | |||||
| boundsMap7 map[string]interface{} | |||||
| boundsMap8 map[string]interface{} | |||||
| boundsMap9 map[string]interface{} | |||||
| waypoints1 []interface{} | |||||
| waypoints2 []interface{} | |||||
| waypoints3 []interface{} | |||||
| waypoints4 []interface{} | |||||
| waypoints5 []interface{} | |||||
| waypoints6 []interface{} | |||||
| waypoints7 []interface{} | |||||
| ) | |||||
| switch format { | |||||
| case "json": | |||||
| boundsMap1 = map[string]interface{}{ | |||||
| "x": float64(300), | |||||
| "y": float64(178), | |||||
| "width": float64(100), | |||||
| "height": float64(80), | |||||
| } | |||||
| boundsMap2 = map[string]interface{}{ | |||||
| "x": float64(455), | |||||
| "y": float64(193), | |||||
| "width": float64(50), | |||||
| "height": float64(50), | |||||
| } | |||||
| boundsMap3 = map[string]interface{}{ | |||||
| "x": float64(300), | |||||
| "y": float64(310), | |||||
| "width": float64(100), | |||||
| "height": float64(80), | |||||
| } | |||||
| boundsMap4 = map[string]interface{}{ | |||||
| "x": float64(550), | |||||
| "y": float64(178), | |||||
| "width": float64(100), | |||||
| "height": float64(80), | |||||
| } | |||||
| boundsMap5 = map[string]interface{}{ | |||||
| "x": float64(550), | |||||
| "y": float64(310), | |||||
| "width": float64(100), | |||||
| "height": float64(80), | |||||
| } | |||||
| boundsMap6 = map[string]interface{}{ | |||||
| "x": float64(632), | |||||
| "y": float64(372), | |||||
| "width": float64(36), | |||||
| "height": float64(36), | |||||
| } | |||||
| boundsMap7 = map[string]interface{}{ | |||||
| "x": float64(722), | |||||
| "y": float64(200), | |||||
| "width": float64(36), | |||||
| "height": float64(36), | |||||
| } | |||||
| boundsMap8 = map[string]interface{}{ | |||||
| "x": float64(722), | |||||
| "y": float64(372), | |||||
| "width": float64(36), | |||||
| "height": float64(36), | |||||
| } | |||||
| boundsMap9 = map[string]interface{}{ | |||||
| "x": float64(812), | |||||
| "y": float64(372), | |||||
| "width": float64(36), | |||||
| "height": float64(36), | |||||
| } | |||||
| waypoints1 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(400), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(400), | |||||
| "y": float64(218), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(435), "y": float64(218)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(455), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(455), | |||||
| "y": float64(218), | |||||
| }, | |||||
| } | |||||
| waypoints2 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(505), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(505), | |||||
| "y": float64(218), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(530), "y": float64(218)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(550), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(550), | |||||
| "y": float64(218), | |||||
| }, | |||||
| } | |||||
| waypoints3 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(480), | |||||
| "y": float64(243), | |||||
| }, | |||||
| "x": float64(480), | |||||
| "y": float64(243), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(600), "y": float64(290)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(600), | |||||
| "y": float64(310), | |||||
| }, | |||||
| "x": float64(600), | |||||
| "y": float64(310), | |||||
| }, | |||||
| } | |||||
| waypoints4 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(650), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(650), | |||||
| "y": float64(218), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(702), "y": float64(218)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(722), | |||||
| "y": float64(218), | |||||
| }, | |||||
| "x": float64(722), | |||||
| "y": float64(218), | |||||
| }, | |||||
| } | |||||
| waypoints5 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(668), | |||||
| "y": float64(390), | |||||
| }, | |||||
| "x": float64(668), | |||||
| "y": float64(390), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(702), "y": float64(390)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(722), | |||||
| "y": float64(390), | |||||
| }, | |||||
| "x": float64(722), | |||||
| "y": float64(390), | |||||
| }, | |||||
| } | |||||
| waypoints6 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(600), | |||||
| "y": float64(310), | |||||
| }, | |||||
| "x": float64(600), | |||||
| "y": float64(310), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(740), "y": float64(256)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(740), | |||||
| "y": float64(236), | |||||
| }, | |||||
| "x": float64(740), | |||||
| "y": float64(236), | |||||
| }, | |||||
| } | |||||
| waypoints7 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(758), | |||||
| "y": float64(390), | |||||
| }, | |||||
| "x": float64(758), | |||||
| "y": float64(390), | |||||
| }, | |||||
| map[string]interface{}{"x": float64(792), "y": float64(390)}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": float64(812), | |||||
| "y": float64(390), | |||||
| }, | |||||
| "x": float64(812), | |||||
| "y": float64(390), | |||||
| }, | |||||
| } | |||||
| case "yaml": | |||||
| boundsMap1 = map[string]interface{}{ | |||||
| "x": 300, | |||||
| "y": 178, | |||||
| "width": 100, | |||||
| "height": 80, | |||||
| } | |||||
| boundsMap2 = map[string]interface{}{ | |||||
| "x": 455, | |||||
| "y": 193, | |||||
| "width": 50, | |||||
| "height": 50, | |||||
| } | |||||
| boundsMap3 = map[string]interface{}{ | |||||
| "x": 300, | |||||
| "y": 310, | |||||
| "width": 100, | |||||
| "height": 80, | |||||
| } | |||||
| boundsMap4 = map[string]interface{}{ | |||||
| "x": 550, | |||||
| "y": 178, | |||||
| "width": 100, | |||||
| "height": 80, | |||||
| } | |||||
| boundsMap5 = map[string]interface{}{ | |||||
| "x": 550, | |||||
| "y": 310, | |||||
| "width": 100, | |||||
| "height": 80, | |||||
| } | |||||
| boundsMap6 = map[string]interface{}{ | |||||
| "x": 632, | |||||
| "y": 372, | |||||
| "width": 36, | |||||
| "height": 36, | |||||
| } | |||||
| boundsMap7 = map[string]interface{}{ | |||||
| "x": 722, | |||||
| "y": 200, | |||||
| "width": 36, | |||||
| "height": 36, | |||||
| } | |||||
| boundsMap8 = map[string]interface{}{ | |||||
| "x": 722, | |||||
| "y": 372, | |||||
| "width": 36, | |||||
| "height": 36, | |||||
| } | |||||
| boundsMap9 = map[string]interface{}{ | |||||
| "x": 812, | |||||
| "y": 372, | |||||
| "width": 36, | |||||
| "height": 36, | |||||
| } | |||||
| waypoints1 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 400, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 400, | |||||
| "y": 218, | |||||
| }, | |||||
| map[string]interface{}{"x": 435, "y": 218}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 455, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 455, | |||||
| "y": 218, | |||||
| }, | |||||
| } | |||||
| waypoints2 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 505, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 505, | |||||
| "y": 218, | |||||
| }, | |||||
| map[string]interface{}{"x": 530, "y": 218}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 550, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 550, | |||||
| "y": 218, | |||||
| }, | |||||
| } | |||||
| waypoints3 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 480, | |||||
| "y": 243, | |||||
| }, | |||||
| "x": 480, | |||||
| "y": 243, | |||||
| }, | |||||
| map[string]interface{}{"x": 600, "y": 290}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 600, | |||||
| "y": 310, | |||||
| }, | |||||
| "x": 600, | |||||
| "y": 310, | |||||
| }, | |||||
| } | |||||
| waypoints4 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 650, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 650, | |||||
| "y": 218, | |||||
| }, | |||||
| map[string]interface{}{"x": 702, "y": 218}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 722, | |||||
| "y": 218, | |||||
| }, | |||||
| "x": 722, | |||||
| "y": 218, | |||||
| }, | |||||
| } | |||||
| waypoints5 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 668, | |||||
| "y": 390, | |||||
| }, | |||||
| "x": 668, | |||||
| "y": 390, | |||||
| }, | |||||
| map[string]interface{}{"x": 702, "y": 390}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 722, | |||||
| "y": 390, | |||||
| }, | |||||
| "x": 722, | |||||
| "y": 390, | |||||
| }, | |||||
| } | |||||
| waypoints6 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 600, | |||||
| "y": 310, | |||||
| }, | |||||
| "x": 600, | |||||
| "y": 310, | |||||
| }, | |||||
| map[string]interface{}{"x": 740, "y": 256}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 740, | |||||
| "y": 236, | |||||
| }, | |||||
| "x": 740, | |||||
| "y": 236, | |||||
| }, | |||||
| } | |||||
| waypoints7 = []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 758, | |||||
| "y": 390, | |||||
| }, | |||||
| "x": 758, | |||||
| "y": 390, | |||||
| }, | |||||
| map[string]interface{}{"x": 792, "y": 390}, | |||||
| map[string]interface{}{ | |||||
| "original": map[string]interface{}{ | |||||
| "x": 812, | |||||
| "y": 390, | |||||
| }, | |||||
| "x": 812, | |||||
| "y": 390, | |||||
| }, | |||||
| } | |||||
| } | |||||
| return &statemachine.StateMachineObject{ | |||||
| Name: "StateMachineNewDesigner", | |||||
| Comment: "This state machine is modeled by designer tools.", | |||||
| Version: "0.0.1", | |||||
| StartState: "ServiceTask-a9h2o51", | |||||
| RecoverStrategy: "", | |||||
| Persist: false, | |||||
| RetryPersistModeUpdate: false, | |||||
| CompensatePersistModeUpdate: false, | |||||
| Type: "", | |||||
| States: map[string]interface{}{ | |||||
| "ServiceTask-a9h2o51": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap1, | |||||
| }, | |||||
| "Name": "ServiceTask-a9h2o51", | |||||
| "IsForCompensation": false, | |||||
| "Input": []interface{}{map[string]interface{}{}}, | |||||
| "Output": map[string]interface{}{}, | |||||
| "Status": map[string]interface{}{}, | |||||
| "Retry": []interface{}{}, | |||||
| "ServiceName": "", | |||||
| "ServiceMethod": "", | |||||
| "Type": "ServiceTask", | |||||
| "Next": "Choice-4ajl8nt", | |||||
| "edge": map[string]interface{}{ | |||||
| "Choice-4ajl8nt": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints1, | |||||
| "source": "ServiceTask-a9h2o51", | |||||
| "target": "Choice-4ajl8nt", | |||||
| }, | |||||
| "Type": "Transition", | |||||
| }, | |||||
| }, | |||||
| "CompensateState": "CompensateFirstState", | |||||
| }, | |||||
| "Choice-4ajl8nt": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap2, | |||||
| }, | |||||
| "Name": "Choice-4ajl8nt", | |||||
| "Type": "Choice", | |||||
| "Choices": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "Expression": "", | |||||
| "Next": "SubStateMachine-cauj9uy", | |||||
| }, | |||||
| map[string]interface{}{ | |||||
| "Expression": "", | |||||
| "Next": "ServiceTask-vdij28l", | |||||
| }, | |||||
| }, | |||||
| "Default": "SubStateMachine-cauj9uy", | |||||
| "edge": map[string]interface{}{ | |||||
| "SubStateMachine-cauj9uy": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints2, | |||||
| "source": "Choice-4ajl8nt", | |||||
| "target": "SubStateMachine-cauj9uy", | |||||
| }, | |||||
| "Type": "ChoiceEntry", | |||||
| }, | |||||
| "ServiceTask-vdij28l": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints3, | |||||
| "source": "Choice-4ajl8nt", | |||||
| "target": "ServiceTask-vdij28l", | |||||
| }, | |||||
| "Type": "ChoiceEntry", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "CompensateFirstState": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap3, | |||||
| }, | |||||
| "Name": "CompensateFirstState", | |||||
| "IsForCompensation": true, | |||||
| "Input": []interface{}{map[string]interface{}{}}, | |||||
| "Output": map[string]interface{}{}, | |||||
| "Status": map[string]interface{}{}, | |||||
| "Retry": []interface{}{}, | |||||
| "ServiceName": "", | |||||
| "ServiceMethod": "", | |||||
| "Type": "ServiceTask", | |||||
| }, | |||||
| "SubStateMachine-cauj9uy": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap4, | |||||
| }, | |||||
| "Name": "SubStateMachine-cauj9uy", | |||||
| "IsForCompensation": false, | |||||
| "Input": []interface{}{map[string]interface{}{}}, | |||||
| "Output": map[string]interface{}{}, | |||||
| "Status": map[string]interface{}{}, | |||||
| "Retry": []interface{}{}, | |||||
| "StateMachineName": "", | |||||
| "Type": "SubStateMachine", | |||||
| "Next": "Succeed-5x3z98u", | |||||
| "edge": map[string]interface{}{ | |||||
| "Succeed-5x3z98u": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints4, | |||||
| "source": "SubStateMachine-cauj9uy", | |||||
| "target": "Succeed-5x3z98u", | |||||
| }, | |||||
| "Type": "Transition", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "ServiceTask-vdij28l": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap5, | |||||
| }, | |||||
| "Name": "ServiceTask-vdij28l", | |||||
| "IsForCompensation": false, | |||||
| "Input": []interface{}{map[string]interface{}{}}, | |||||
| "Output": map[string]interface{}{}, | |||||
| "Status": map[string]interface{}{}, | |||||
| "Retry": []interface{}{}, | |||||
| "ServiceName": "", | |||||
| "ServiceMethod": "", | |||||
| "Catch": []interface{}{ | |||||
| map[string]interface{}{ | |||||
| "Exceptions": []interface{}{}, | |||||
| "Next": "CompensationTrigger-uldp2ou", | |||||
| }, | |||||
| }, | |||||
| "Type": "ServiceTask", | |||||
| "catch": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap6, | |||||
| }, | |||||
| "edge": map[string]interface{}{ | |||||
| "CompensationTrigger-uldp2ou": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints5, | |||||
| "source": "ServiceTask-vdij28l", | |||||
| "target": "CompensationTrigger-uldp2ou", | |||||
| }, | |||||
| "Type": "ExceptionMatch", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "Next": "Succeed-5x3z98u", | |||||
| "edge": map[string]interface{}{ | |||||
| "Succeed-5x3z98u": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints6, | |||||
| "source": "ServiceTask-vdij28l", | |||||
| "target": "Succeed-5x3z98u", | |||||
| }, | |||||
| "Type": "Transition", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "Succeed-5x3z98u": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap7, | |||||
| }, | |||||
| "Name": "Succeed-5x3z98u", | |||||
| "Type": "Succeed", | |||||
| }, | |||||
| "CompensationTrigger-uldp2ou": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap8, | |||||
| }, | |||||
| "Name": "CompensationTrigger-uldp2ou", | |||||
| "Type": "CompensationTrigger", | |||||
| "Next": "Fail-9roxcv5", | |||||
| "edge": map[string]interface{}{ | |||||
| "Fail-9roxcv5": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "waypoints": waypoints7, | |||||
| "source": "CompensationTrigger-uldp2ou", | |||||
| "target": "Fail-9roxcv5", | |||||
| }, | |||||
| "Type": "Transition", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "Fail-9roxcv5": map[string]interface{}{ | |||||
| "style": map[string]interface{}{ | |||||
| "bounds": boundsMap9, | |||||
| }, | |||||
| "Name": "Fail-9roxcv5", | |||||
| "ErrorCode": "", | |||||
| "Message": "", | |||||
| "Type": "Fail", | |||||
| }, | |||||
| }, | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,130 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type JSONStateMachineParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewJSONStateMachineParser() *JSONStateMachineParser { | |||||
| return &JSONStateMachineParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (stateMachineParser JSONStateMachineParser) GetType() string { | |||||
| return "JSON" | |||||
| } | |||||
| func (stateMachineParser JSONStateMachineParser) Parse(content string) (statelang.StateMachine, error) { | |||||
| stateMachineJsonObject, err := NewStateMachineConfigParser().Parse([]byte(content)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| stateMachine := statelang.NewStateMachineImpl() | |||||
| stateMachine.SetName(stateMachineJsonObject.Name) | |||||
| stateMachine.SetComment(stateMachineJsonObject.Comment) | |||||
| stateMachine.SetVersion(stateMachineJsonObject.Version) | |||||
| stateMachine.SetStartState(stateMachineJsonObject.StartState) | |||||
| stateMachine.SetPersist(stateMachineJsonObject.Persist) | |||||
| if stateMachineJsonObject.Type != "" { | |||||
| stateMachine.SetType(stateMachineJsonObject.Type) | |||||
| } | |||||
| if stateMachineJsonObject.RecoverStrategy != "" { | |||||
| recoverStrategy, ok := statelang.ValueOfRecoverStrategy(stateMachineJsonObject.RecoverStrategy) | |||||
| if !ok { | |||||
| return nil, errors.New("Not support " + stateMachineJsonObject.RecoverStrategy) | |||||
| } | |||||
| stateMachine.SetRecoverStrategy(recoverStrategy) | |||||
| } | |||||
| stateParserFactory := NewDefaultStateParserFactory() | |||||
| stateParserFactory.InitDefaultStateParser() | |||||
| for stateName, v := range stateMachineJsonObject.States { | |||||
| stateMap, ok := v.(map[string]interface{}) | |||||
| if !ok { | |||||
| return nil, errors.New("State [" + stateName + "] scheme illegal, required map") | |||||
| } | |||||
| stateType, ok := stateMap["Type"].(string) | |||||
| if !ok { | |||||
| return nil, errors.New("State [" + stateName + "] Type illegal, required string") | |||||
| } | |||||
| //stateMap | |||||
| stateParser := stateParserFactory.GetStateParser(stateType) | |||||
| if stateParser == nil { | |||||
| return nil, errors.New("State Type [" + stateType + "] is not support") | |||||
| } | |||||
| _, stateExist := stateMachine.States()[stateName] | |||||
| if stateExist { | |||||
| return nil, errors.New("State [name:" + stateName + "] already exists") | |||||
| } | |||||
| state, err := stateParser.Parse(stateName, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| state.SetStateMachine(stateMachine) | |||||
| stateMachine.States()[stateName] = state | |||||
| } | |||||
| for _, stateValue := range stateMachine.States() { | |||||
| if stateMachineParser.isTaskState(stateValue.Type()) { | |||||
| stateMachineParser.setForCompensation(stateValue, stateMachine) | |||||
| } | |||||
| } | |||||
| return stateMachine, nil | |||||
| } | |||||
| func (stateMachineParser JSONStateMachineParser) setForCompensation(stateValue statelang.State, stateMachine *statelang.StateMachineImpl) { | |||||
| if stateValue.Type() == constant.StateTypeServiceTask { | |||||
| serviceTaskStateImpl, ok := stateValue.(*state.ServiceTaskStateImpl) | |||||
| if ok { | |||||
| if serviceTaskStateImpl.CompensateState() != "" { | |||||
| compState := stateMachine.States()[serviceTaskStateImpl.CompensateState()] | |||||
| if stateMachineParser.isTaskState(compState.Type()) { | |||||
| compStateImpl, ok := compState.(*state.ServiceTaskStateImpl) | |||||
| if ok { | |||||
| compStateImpl.SetForCompensation(true) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| func (stateMachineParser JSONStateMachineParser) isTaskState(stateType string) bool { | |||||
| if stateType == constant.StateTypeServiceTask { | |||||
| return true | |||||
| } | |||||
| return false | |||||
| } | |||||
| @@ -0,0 +1,127 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "os" | |||||
| "testing" | |||||
| ) | |||||
| func readFileContent(filePath string) (string, error) { | |||||
| content, err := os.ReadFile(filePath) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| return string(content), nil | |||||
| } | |||||
| func TestParseChoice(t *testing.T) { | |||||
| parser := NewJSONStateMachineParser() | |||||
| tests := []struct { | |||||
| name string | |||||
| configFilePath string | |||||
| }{ | |||||
| { | |||||
| name: "JSON Simple: StateLang With Choice", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statelang_with_choice.json", | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple: StateLang With Choice", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statelang_with_choice.yaml", | |||||
| }, | |||||
| } | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| content, err := readFileContent(tt.configFilePath) | |||||
| if err != nil { | |||||
| t.Error("read file fail: " + err.Error()) | |||||
| return | |||||
| } | |||||
| _, err = parser.Parse(content) | |||||
| if err != nil { | |||||
| t.Error("parse fail: " + err.Error()) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| func TestParseServiceTaskForSimpleStateMachine(t *testing.T) { | |||||
| parser := NewJSONStateMachineParser() | |||||
| tests := []struct { | |||||
| name string | |||||
| configFilePath string | |||||
| }{ | |||||
| { | |||||
| name: "JSON Simple: StateMachine", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statemachine.json", | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple: StateMachine", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/simple_statemachine.yaml", | |||||
| }, | |||||
| } | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| content, err := readFileContent(tt.configFilePath) | |||||
| if err != nil { | |||||
| t.Error("read file fail: " + err.Error()) | |||||
| return | |||||
| } | |||||
| _, err = parser.Parse(content) | |||||
| if err != nil { | |||||
| t.Error("parse fail: " + err.Error()) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| func TestParseServiceTaskForNewDesigner(t *testing.T) { | |||||
| parser := NewJSONStateMachineParser() | |||||
| tests := []struct { | |||||
| name string | |||||
| configFilePath string | |||||
| }{ | |||||
| { | |||||
| name: "JSON Simple: StateMachine New Designer", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/state_machine_new_designer.json", | |||||
| }, | |||||
| { | |||||
| name: "YAML Simple: StateMachine New Designer", | |||||
| configFilePath: "../../../../../testdata/saga/statelang/state_machine_new_designer.yaml", | |||||
| }, | |||||
| } | |||||
| for _, tt := range tests { | |||||
| t.Run(tt.name, func(t *testing.T) { | |||||
| content, err := readFileContent(tt.configFilePath) | |||||
| if err != nil { | |||||
| t.Error("read file fail: " + err.Error()) | |||||
| return | |||||
| } | |||||
| _, err = parser.Parse(content) | |||||
| if err != nil { | |||||
| t.Error("parse fail: " + err.Error()) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,253 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "strconv" | |||||
| "strings" | |||||
| "sync" | |||||
| ) | |||||
| type StateMachineParser interface { | |||||
| GetType() string | |||||
| Parse(content string) (statelang.StateMachine, error) | |||||
| } | |||||
| type StateParser interface { | |||||
| StateType() string | |||||
| Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) | |||||
| } | |||||
| type BaseStateParser struct { | |||||
| } | |||||
| func NewBaseStateParser() *BaseStateParser { | |||||
| return &BaseStateParser{} | |||||
| } | |||||
| func (b BaseStateParser) ParseBaseAttributes(stateName string, state statelang.State, stateMap map[string]interface{}) error { | |||||
| state.SetName(stateName) | |||||
| comment, err := b.GetStringOrDefault(stateName, stateMap, "Comment", "") | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetComment(comment) | |||||
| next, err := b.GetStringOrDefault(stateName, stateMap, "Next", "") | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetNext(next) | |||||
| return nil | |||||
| } | |||||
| func (b BaseStateParser) GetString(stateName string, stateMap map[string]interface{}, key string) (string, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| var result string | |||||
| return result, errors.New("State [" + stateName + "] " + key + " not exist") | |||||
| } | |||||
| valueAsString, ok := value.(string) | |||||
| if !ok { | |||||
| var s string | |||||
| return s, errors.New("State [" + stateName + "] " + key + " illegal, required string") | |||||
| } | |||||
| return valueAsString, nil | |||||
| } | |||||
| func (b BaseStateParser) GetStringOrDefault(stateName string, stateMap map[string]interface{}, key string, defaultValue string) (string, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| valueAsString, ok := value.(string) | |||||
| if !ok { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required string") | |||||
| } | |||||
| return valueAsString, nil | |||||
| } | |||||
| func (b BaseStateParser) GetSlice(stateName string, stateMap map[string]interface{}, key string) ([]interface{}, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| var result []interface{} | |||||
| return result, errors.New("State [" + stateName + "] " + key + " not exist") | |||||
| } | |||||
| valueAsSlice, ok := value.([]interface{}) | |||||
| if !ok { | |||||
| var slice []interface{} | |||||
| return slice, errors.New("State [" + stateName + "] " + key + " illegal, required []interface{}") | |||||
| } | |||||
| return valueAsSlice, nil | |||||
| } | |||||
| func (b BaseStateParser) GetSliceOrDefault(stateName string, stateMap map[string]interface{}, key string, defaultValue []interface{}) ([]interface{}, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| valueAsSlice, ok := value.([]interface{}) | |||||
| if !ok { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required []interface{}") | |||||
| } | |||||
| return valueAsSlice, nil | |||||
| } | |||||
| func (b BaseStateParser) GetMapOrDefault(stateMap map[string]interface{}, key string, defaultValue map[string]interface{}) (map[string]interface{}, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| valueAsMap, ok := value.(map[string]interface{}) | |||||
| if !ok { | |||||
| return defaultValue, nil | |||||
| } | |||||
| return valueAsMap, nil | |||||
| } | |||||
| func (b BaseStateParser) GetBool(stateName string, stateMap map[string]interface{}, key string) (bool, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return false, errors.New("State [" + stateName + "] " + key + " not exist") | |||||
| } | |||||
| valueAsBool, ok := value.(bool) | |||||
| if !ok { | |||||
| return false, errors.New("State [" + stateName + "] " + key + " illegal, required bool") | |||||
| } | |||||
| return valueAsBool, nil | |||||
| } | |||||
| func (b BaseStateParser) GetBoolOrDefault(stateName string, stateMap map[string]interface{}, key string, defaultValue bool) (bool, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| valueAsBool, ok := value.(bool) | |||||
| if !ok { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required bool") | |||||
| } | |||||
| return valueAsBool, nil | |||||
| } | |||||
| func (b BaseStateParser) GetIntOrDefault(stateName string, stateMap map[string]interface{}, key string, defaultValue int) (int, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| // use float64 conversion when the configuration file is json, and use int conversion when the configuration file is yaml | |||||
| valueAsFloat64, okToFloat64 := value.(float64) | |||||
| valueAsInt, okToInt := value.(int) | |||||
| if !okToFloat64 && !okToInt { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required int") | |||||
| } | |||||
| if okToFloat64 { | |||||
| floatStr := strconv.FormatFloat(valueAsFloat64, 'f', -1, 64) | |||||
| if strings.Contains(floatStr, ".") { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required int") | |||||
| } | |||||
| return int(valueAsFloat64), nil | |||||
| } | |||||
| return valueAsInt, nil | |||||
| } | |||||
| func (b BaseStateParser) GetFloat64OrDefault(stateName string, stateMap map[string]interface{}, key string, defaultValue float64) (float64, error) { | |||||
| value := stateMap[key] | |||||
| if value == nil { | |||||
| return defaultValue, nil | |||||
| } | |||||
| // use float64 conversion when the configuration file is json, and use int conversion when the configuration file is yaml | |||||
| valueAsFloat64, okToFloat64 := value.(float64) | |||||
| valueAsInt, okToInt := value.(int) | |||||
| if !okToFloat64 && !okToInt { | |||||
| return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required float64") | |||||
| } | |||||
| if okToFloat64 { | |||||
| return valueAsFloat64, nil | |||||
| } | |||||
| return float64(valueAsInt), nil | |||||
| } | |||||
| type StateParserFactory interface { | |||||
| RegistryStateParser(stateType string, stateParser StateParser) | |||||
| GetStateParser(stateType string) StateParser | |||||
| } | |||||
| type DefaultStateParserFactory struct { | |||||
| stateParserMap map[string]StateParser | |||||
| mutex sync.Mutex | |||||
| } | |||||
| func NewDefaultStateParserFactory() *DefaultStateParserFactory { | |||||
| var stateParserMap map[string]StateParser = make(map[string]StateParser) | |||||
| return &DefaultStateParserFactory{ | |||||
| stateParserMap: stateParserMap, | |||||
| } | |||||
| } | |||||
| // InitDefaultStateParser init StateParser by default | |||||
| func (d *DefaultStateParserFactory) InitDefaultStateParser() { | |||||
| choiceStateParser := NewChoiceStateParser() | |||||
| serviceTaskStateParser := NewServiceTaskStateParser() | |||||
| subStateMachineParser := NewSubStateMachineParser() | |||||
| succeedEndStateParser := NewSucceedEndStateParser() | |||||
| compensationTriggerStateParser := NewCompensationTriggerStateParser() | |||||
| failEndStateParser := NewFailEndStateParser() | |||||
| scriptTaskStateParser := NewScriptTaskStateParser() | |||||
| d.RegistryStateParser(choiceStateParser.StateType(), choiceStateParser) | |||||
| d.RegistryStateParser(serviceTaskStateParser.StateType(), serviceTaskStateParser) | |||||
| d.RegistryStateParser(subStateMachineParser.StateType(), subStateMachineParser) | |||||
| d.RegistryStateParser(succeedEndStateParser.StateType(), succeedEndStateParser) | |||||
| d.RegistryStateParser(compensationTriggerStateParser.StateType(), compensationTriggerStateParser) | |||||
| d.RegistryStateParser(compensationTriggerStateParser.StateType(), compensationTriggerStateParser) | |||||
| d.RegistryStateParser(failEndStateParser.StateType(), failEndStateParser) | |||||
| d.RegistryStateParser(scriptTaskStateParser.StateType(), scriptTaskStateParser) | |||||
| } | |||||
| func (d *DefaultStateParserFactory) RegistryStateParser(stateType string, stateParser StateParser) { | |||||
| d.mutex.Lock() | |||||
| defer d.mutex.Unlock() | |||||
| d.stateParserMap[stateType] = stateParser | |||||
| } | |||||
| func (d *DefaultStateParserFactory) GetStateParser(stateType string) StateParser { | |||||
| return d.stateParserMap[stateType] | |||||
| } | |||||
| @@ -0,0 +1,101 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type SubStateMachineParser struct { | |||||
| *AbstractTaskStateParser | |||||
| } | |||||
| func NewSubStateMachineParser() *SubStateMachineParser { | |||||
| return &SubStateMachineParser{ | |||||
| NewAbstractTaskStateParser(), | |||||
| } | |||||
| } | |||||
| func (s SubStateMachineParser) StateType() string { | |||||
| return constant.StateTypeSubStateMachine | |||||
| } | |||||
| func (s SubStateMachineParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| subStateMachineImpl := state.NewSubStateMachineImpl() | |||||
| err := s.ParseTaskAttributes(stateName, subStateMachineImpl.AbstractTaskState, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| stateMachineName, err := s.BaseStateParser.GetString(stateName, stateMap, "StateMachineName") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| subStateMachineImpl.SetName(stateMachineName) | |||||
| if subStateMachineImpl.CompensateState() == "" { | |||||
| // build default SubStateMachine compensate state | |||||
| compensateSubStateMachineStateParser := NewCompensateSubStateMachineStateParser() | |||||
| compensateState, err := compensateSubStateMachineStateParser.Parse(stateName, nil) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| compensateStateImpl, ok := compensateState.(state.TaskState) | |||||
| if !ok { | |||||
| return nil, errors.New(fmt.Sprintf("State [name:%s] has wrong compensateState type", stateName)) | |||||
| } | |||||
| subStateMachineImpl.SetCompensateStateImpl(compensateStateImpl) | |||||
| subStateMachineImpl.SetCompensateState(compensateStateImpl.Name()) | |||||
| } | |||||
| return subStateMachineImpl, nil | |||||
| } | |||||
| type CompensateSubStateMachineStateParser struct { | |||||
| *AbstractTaskStateParser | |||||
| } | |||||
| func NewCompensateSubStateMachineStateParser() *CompensateSubStateMachineStateParser { | |||||
| return &CompensateSubStateMachineStateParser{ | |||||
| NewAbstractTaskStateParser(), | |||||
| } | |||||
| } | |||||
| func (c CompensateSubStateMachineStateParser) StateType() string { | |||||
| return constant.StateTypeCompensateSubMachine | |||||
| } | |||||
| func (c CompensateSubStateMachineStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| compensateSubStateMachineStateImpl := state.NewCompensateSubStateMachineStateImpl() | |||||
| compensateSubStateMachineStateImpl.SetForCompensation(true) | |||||
| if stateMap != nil { | |||||
| err := c.ParseTaskAttributes(stateName, compensateSubStateMachineStateImpl.ServiceTaskStateImpl.AbstractTaskState, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| if compensateSubStateMachineStateImpl.Name() == "" { | |||||
| compensateSubStateMachineStateImpl.SetName(constant.CompensateSubMachineStateNamePrefix + compensateSubStateMachineStateImpl.Hashcode()) | |||||
| } | |||||
| return compensateSubStateMachineStateImpl, nil | |||||
| } | |||||
| @@ -0,0 +1,347 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package parser | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||||
| ) | |||||
| type AbstractTaskStateParser struct { | |||||
| *BaseStateParser | |||||
| } | |||||
| func NewAbstractTaskStateParser() *AbstractTaskStateParser { | |||||
| return &AbstractTaskStateParser{ | |||||
| &BaseStateParser{}, | |||||
| } | |||||
| } | |||||
| func (a *AbstractTaskStateParser) ParseTaskAttributes(stateName string, state *state.AbstractTaskState, stateMap map[string]interface{}) error { | |||||
| err := a.ParseBaseAttributes(state.Name(), state.BaseState, stateMap) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| compensateState, err := a.GetStringOrDefault(stateName, stateMap, "CompensateState", "") | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetCompensateState(compensateState) | |||||
| isForCompensation, err := a.GetBoolOrDefault(stateName, stateMap, "IsForCompensation", false) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetForCompensation(isForCompensation) | |||||
| isForUpdate, err := a.GetBoolOrDefault(stateName, stateMap, "IsForUpdate", false) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetForUpdate(isForUpdate) | |||||
| isPersist, err := a.GetBoolOrDefault(stateName, stateMap, "IsPersist", false) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetPersist(isPersist) | |||||
| isRetryPersistModeUpdate, err := a.GetBoolOrDefault(stateName, stateMap, "IsRetryPersistModeUpdate", false) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetRetryPersistModeUpdate(isRetryPersistModeUpdate) | |||||
| isCompensatePersistModeUpdate, err := a.GetBoolOrDefault(stateName, stateMap, "IsCompensatePersistModeUpdate", false) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetCompensatePersistModeUpdate(isCompensatePersistModeUpdate) | |||||
| retryInterfaces, err := a.GetSliceOrDefault(stateName, stateMap, "Retry", nil) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if retryInterfaces != nil { | |||||
| retries, err := a.parseRetries(state.Name(), retryInterfaces) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetRetry(retries) | |||||
| } | |||||
| catchInterfaces, err := a.GetSliceOrDefault(stateName, stateMap, "Catch", nil) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if catchInterfaces != nil { | |||||
| catches, err := a.parseCatches(state.Name(), catchInterfaces) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| state.SetCatches(catches) | |||||
| } | |||||
| inputInterfaces, err := a.GetSliceOrDefault(stateName, stateMap, "Input", nil) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if inputInterfaces != nil { | |||||
| state.SetInput(inputInterfaces) | |||||
| } | |||||
| output, err := a.GetMapOrDefault(stateMap, "Output", nil) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if output != nil { | |||||
| state.SetOutput(output) | |||||
| } | |||||
| statusMap, ok := stateMap["Status"].(map[string]string) | |||||
| if ok { | |||||
| state.SetStatus(statusMap) | |||||
| } | |||||
| loopMap, ok := stateMap["Loop"].(map[string]interface{}) | |||||
| if ok { | |||||
| loop := a.parseLoop(stateName, loopMap) | |||||
| state.SetLoop(loop) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (a *AbstractTaskStateParser) parseLoop(stateName string, loopMap map[string]interface{}) state.Loop { | |||||
| loopImpl := &state.LoopImpl{} | |||||
| parallel, err := a.GetIntOrDefault(stateName, loopMap, "Parallel", 1) | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| loopImpl.SetParallel(parallel) | |||||
| collection, err := a.GetStringOrDefault(stateName, loopMap, "Collection", "") | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| loopImpl.SetCollection(collection) | |||||
| elementVariableName, err := a.GetStringOrDefault(stateName, loopMap, "ElementVariableName", "loopElement") | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| loopImpl.SetElementVariableName(elementVariableName) | |||||
| elementIndexName, err := a.GetStringOrDefault(stateName, loopMap, "ElementIndexName", "loopCounter") | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| loopImpl.SetElementIndexName(elementIndexName) | |||||
| completionCondition, err := a.GetStringOrDefault(stateName, loopMap, "CompletionCondition", "[nrOfInstances] == [nrOfCompletedInstances]") | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| loopImpl.SetElementIndexName(completionCondition) | |||||
| return loopImpl | |||||
| } | |||||
| func (a *AbstractTaskStateParser) parseRetries(stateName string, retryInterfaces []interface{}) ([]state.Retry, error) { | |||||
| retries := make([]state.Retry, 0) | |||||
| for _, retryInterface := range retryInterfaces { | |||||
| retryMap, ok := retryInterface.(map[string]interface{}) | |||||
| if !ok { | |||||
| return nil, errors.New("State [" + stateName + "] " + "Retry illegal, require map[string]interface{}") | |||||
| } | |||||
| retry := &state.RetryImpl{} | |||||
| exceptions, err := a.GetSliceOrDefault(stateName, retryMap, "Exceptions", nil) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if exceptions != nil { | |||||
| errors := make([]string, 0) | |||||
| for _, errorType := range exceptions { | |||||
| errors = append(errors, errorType.(string)) | |||||
| } | |||||
| retry.SetExceptions(errors) | |||||
| } | |||||
| maxAttempts, err := a.GetIntOrDefault(stateName, retryMap, "MaxAttempts", 0) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| retry.SetMaxAttempt(maxAttempts) | |||||
| backoffInterval, err := a.GetFloat64OrDefault(stateName, retryMap, "BackoffInterval", 0) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| retry.SetBackoffRate(backoffInterval) | |||||
| intervalSeconds, err := a.GetFloat64OrDefault(stateName, retryMap, "IntervalSeconds", 0) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| retry.SetIntervalSecond(intervalSeconds) | |||||
| retries = append(retries, retry) | |||||
| } | |||||
| return retries, nil | |||||
| } | |||||
| func (a *AbstractTaskStateParser) parseCatches(stateName string, catchInterfaces []interface{}) ([]state.ExceptionMatch, error) { | |||||
| errorMatches := make([]state.ExceptionMatch, 0, len(catchInterfaces)) | |||||
| for _, catchInterface := range catchInterfaces { | |||||
| catchMap, ok := catchInterface.(map[string]interface{}) | |||||
| if !ok { | |||||
| return nil, errors.New("State [" + stateName + "] " + "Catch illegal, require map[string]interface{}") | |||||
| } | |||||
| errorMatch := &state.ExceptionMatchImpl{} | |||||
| errorInterfaces, err := a.GetSliceOrDefault(stateName, catchMap, "Exceptions", nil) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if errorInterfaces != nil { | |||||
| errorNames := make([]string, 0) | |||||
| for _, errorType := range errorInterfaces { | |||||
| errorNames = append(errorNames, errorType.(string)) | |||||
| } | |||||
| errorMatch.SetExceptions(errorNames) | |||||
| } | |||||
| next, err := a.GetStringOrDefault(stateName, catchMap, "Next", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| errorMatch.SetNext(next) | |||||
| errorMatches = append(errorMatches, errorMatch) | |||||
| } | |||||
| return errorMatches, nil | |||||
| } | |||||
| type ServiceTaskStateParser struct { | |||||
| *AbstractTaskStateParser | |||||
| } | |||||
| func NewServiceTaskStateParser() *ServiceTaskStateParser { | |||||
| return &ServiceTaskStateParser{ | |||||
| NewAbstractTaskStateParser(), | |||||
| } | |||||
| } | |||||
| func (s ServiceTaskStateParser) StateType() string { | |||||
| return constant.StateTypeServiceTask | |||||
| } | |||||
| func (s ServiceTaskStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||||
| err := s.ParseTaskAttributes(stateName, serviceTaskStateImpl.AbstractTaskState, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| serviceName, err := s.GetString(stateName, stateMap, "ServiceName") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| serviceTaskStateImpl.SetServiceName(serviceName) | |||||
| serviceMethod, err := s.GetString(stateName, stateMap, "ServiceMethod") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| serviceTaskStateImpl.SetServiceMethod(serviceMethod) | |||||
| serviceType, err := s.GetStringOrDefault(stateName, stateMap, "ServiceType", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| serviceTaskStateImpl.SetServiceType(serviceType) | |||||
| parameterTypeInterfaces, err := s.GetSliceOrDefault(stateName, stateMap, "ParameterTypes", nil) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if parameterTypeInterfaces != nil { | |||||
| var parameterTypes []string | |||||
| for i := range parameterTypeInterfaces { | |||||
| parameterType, ok := parameterTypeInterfaces[i].(string) | |||||
| if !ok { | |||||
| return nil, errors.New(fmt.Sprintf("State [%s] parameterType required string", stateName)) | |||||
| } | |||||
| parameterTypes = append(parameterTypes, parameterType) | |||||
| } | |||||
| serviceTaskStateImpl.SetParameterTypes(parameterTypes) | |||||
| } | |||||
| isAsync, err := s.GetBoolOrDefault(stateName, stateMap, "IsAsync", false) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| serviceTaskStateImpl.SetIsAsync(isAsync) | |||||
| return serviceTaskStateImpl, nil | |||||
| } | |||||
| type ScriptTaskStateParser struct { | |||||
| *AbstractTaskStateParser | |||||
| } | |||||
| func NewScriptTaskStateParser() *ScriptTaskStateParser { | |||||
| return &ScriptTaskStateParser{ | |||||
| NewAbstractTaskStateParser(), | |||||
| } | |||||
| } | |||||
| func (s ScriptTaskStateParser) StateType() string { | |||||
| return constant.StateTypeScriptTask | |||||
| } | |||||
| func (s ScriptTaskStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||||
| scriptTaskStateImpl := state.NewScriptTaskStateImpl() | |||||
| err := s.ParseTaskAttributes(stateName, scriptTaskStateImpl.AbstractTaskState, stateMap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| scriptType, err := s.GetStringOrDefault(stateName, stateMap, "ScriptType", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if scriptType != "" { | |||||
| scriptTaskStateImpl.SetScriptType(scriptType) | |||||
| } | |||||
| scriptContent, err := s.GetStringOrDefault(stateName, stateMap, "ScriptContent", "") | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| scriptTaskStateImpl.SetScriptContent(scriptContent) | |||||
| scriptTaskStateImpl.SetForCompensation(false) | |||||
| scriptTaskStateImpl.SetForUpdate(false) | |||||
| scriptTaskStateImpl.SetPersist(false) | |||||
| return scriptTaskStateImpl, nil | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package statelang | |||||
| type State interface { | |||||
| Name() string | |||||
| SetName(name string) | |||||
| Comment() string | |||||
| SetComment(comment string) | |||||
| Type() string | |||||
| SetType(typeName string) | |||||
| Next() string | |||||
| SetNext(next string) | |||||
| StateMachine() StateMachine | |||||
| SetStateMachine(machine StateMachine) | |||||
| } | |||||
| type BaseState struct { | |||||
| name string `alias:"Name"` | |||||
| comment string `alias:"Comment"` | |||||
| typeName string `alias:"Type"` | |||||
| next string `alias:"Next"` | |||||
| stateMachine StateMachine | |||||
| } | |||||
| func NewBaseState() *BaseState { | |||||
| return &BaseState{} | |||||
| } | |||||
| func (b *BaseState) Name() string { | |||||
| return b.name | |||||
| } | |||||
| func (b *BaseState) SetName(name string) { | |||||
| b.name = name | |||||
| } | |||||
| func (b *BaseState) Comment() string { | |||||
| return b.comment | |||||
| } | |||||
| func (b *BaseState) SetComment(comment string) { | |||||
| b.comment = comment | |||||
| } | |||||
| func (b *BaseState) Type() string { | |||||
| return b.typeName | |||||
| } | |||||
| func (b *BaseState) SetType(typeName string) { | |||||
| b.typeName = typeName | |||||
| } | |||||
| func (b *BaseState) Next() string { | |||||
| return b.next | |||||
| } | |||||
| func (b *BaseState) SetNext(next string) { | |||||
| b.next = next | |||||
| } | |||||
| func (b *BaseState) StateMachine() StateMachine { | |||||
| return b.stateMachine | |||||
| } | |||||
| func (b *BaseState) SetStateMachine(machine StateMachine) { | |||||
| b.stateMachine = machine | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| type ChoiceState interface { | |||||
| statelang.State | |||||
| Choices() []Choice | |||||
| Default() string | |||||
| } | |||||
| type Choice interface { | |||||
| Expression() string | |||||
| SetExpression(expression string) | |||||
| Next() string | |||||
| SetNext(next string) | |||||
| } | |||||
| type ChoiceStateImpl struct { | |||||
| *statelang.BaseState | |||||
| defaultChoice string `alias:"Default"` | |||||
| choices []Choice `alias:"Choices"` | |||||
| } | |||||
| func NewChoiceStateImpl() *ChoiceStateImpl { | |||||
| return &ChoiceStateImpl{ | |||||
| BaseState: statelang.NewBaseState(), | |||||
| choices: make([]Choice, 0), | |||||
| } | |||||
| } | |||||
| func (choiceState *ChoiceStateImpl) Default() string { | |||||
| return choiceState.defaultChoice | |||||
| } | |||||
| func (choiceState *ChoiceStateImpl) Choices() []Choice { | |||||
| return choiceState.choices | |||||
| } | |||||
| func (choiceState *ChoiceStateImpl) SetDefault(defaultChoice string) { | |||||
| choiceState.defaultChoice = defaultChoice | |||||
| } | |||||
| func (choiceState *ChoiceStateImpl) SetChoices(choices []Choice) { | |||||
| choiceState.choices = choices | |||||
| } | |||||
| type ChoiceImpl struct { | |||||
| expression string | |||||
| next string | |||||
| } | |||||
| func NewChoiceImpl() *ChoiceImpl { | |||||
| return &ChoiceImpl{} | |||||
| } | |||||
| func (c *ChoiceImpl) Expression() string { | |||||
| return c.expression | |||||
| } | |||||
| func (c *ChoiceImpl) SetExpression(expression string) { | |||||
| c.expression = expression | |||||
| } | |||||
| func (c *ChoiceImpl) Next() string { | |||||
| return c.next | |||||
| } | |||||
| func (c *ChoiceImpl) SetNext(next string) { | |||||
| c.next = next | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| type CompensationTriggerState interface { | |||||
| statelang.State | |||||
| } | |||||
| type CompensationTriggerStateImpl struct { | |||||
| *statelang.BaseState | |||||
| } | |||||
| func NewCompensationTriggerStateImpl() *CompensationTriggerStateImpl { | |||||
| s := &CompensationTriggerStateImpl{ | |||||
| BaseState: statelang.NewBaseState(), | |||||
| } | |||||
| s.SetType(constant.StateTypeCompensationTrigger) | |||||
| return s | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| type EndState interface { | |||||
| statelang.State | |||||
| } | |||||
| type SucceedEndState interface { | |||||
| EndState | |||||
| } | |||||
| type SucceedEndStateImpl struct { | |||||
| *statelang.BaseState | |||||
| } | |||||
| func NewSucceedEndStateImpl() *SucceedEndStateImpl { | |||||
| s := &SucceedEndStateImpl{ | |||||
| BaseState: statelang.NewBaseState(), | |||||
| } | |||||
| s.SetType(constant.StateTypeSucceed) | |||||
| return s | |||||
| } | |||||
| type FailEndState interface { | |||||
| EndState | |||||
| ErrorCode() string | |||||
| Message() string | |||||
| } | |||||
| type FailEndStateImpl struct { | |||||
| *statelang.BaseState | |||||
| errorCode string | |||||
| message string | |||||
| } | |||||
| func NewFailEndStateImpl() *FailEndStateImpl { | |||||
| s := &FailEndStateImpl{ | |||||
| BaseState: statelang.NewBaseState(), | |||||
| } | |||||
| s.SetType(constant.StateTypeFail) | |||||
| return s | |||||
| } | |||||
| func (f *FailEndStateImpl) ErrorCode() string { | |||||
| return f.errorCode | |||||
| } | |||||
| func (f *FailEndStateImpl) SetErrorCode(errorCode string) { | |||||
| f.errorCode = errorCode | |||||
| } | |||||
| func (f *FailEndStateImpl) Message() string { | |||||
| return f.message | |||||
| } | |||||
| func (f *FailEndStateImpl) SetMessage(message string) { | |||||
| f.message = message | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| ) | |||||
| type LoopStartState interface { | |||||
| statelang.State | |||||
| } | |||||
| type LoopStartStateImpl struct { | |||||
| *statelang.BaseState | |||||
| } | |||||
| func NewLoopStartStateImpl() *LoopStartStateImpl { | |||||
| baseState := statelang.NewBaseState() | |||||
| baseState.SetType(constant.StateTypeLoopStart) | |||||
| return &LoopStartStateImpl{ | |||||
| BaseState: baseState, | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,86 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import ( | |||||
| "github.com/google/uuid" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| ) | |||||
| type SubStateMachine interface { | |||||
| TaskState | |||||
| StateMachineName() string | |||||
| CompensateStateImpl() TaskState | |||||
| } | |||||
| type SubStateMachineImpl struct { | |||||
| *ServiceTaskStateImpl | |||||
| stateMachineName string | |||||
| compensateState TaskState | |||||
| } | |||||
| func NewSubStateMachineImpl() *SubStateMachineImpl { | |||||
| return &SubStateMachineImpl{ | |||||
| ServiceTaskStateImpl: NewServiceTaskStateImpl(), | |||||
| } | |||||
| } | |||||
| func (s *SubStateMachineImpl) StateMachineName() string { | |||||
| return s.stateMachineName | |||||
| } | |||||
| func (s *SubStateMachineImpl) SetStateMachineName(stateMachineName string) { | |||||
| s.stateMachineName = stateMachineName | |||||
| } | |||||
| func (s *SubStateMachineImpl) CompensateStateImpl() TaskState { | |||||
| return s.compensateState | |||||
| } | |||||
| func (s *SubStateMachineImpl) SetCompensateStateImpl(compensateState TaskState) { | |||||
| s.compensateState = compensateState | |||||
| } | |||||
| type CompensateSubStateMachineState interface { | |||||
| ServiceTaskState | |||||
| } | |||||
| type CompensateSubStateMachineStateImpl struct { | |||||
| *ServiceTaskStateImpl | |||||
| hashcode string | |||||
| } | |||||
| func NewCompensateSubStateMachineStateImpl() *CompensateSubStateMachineStateImpl { | |||||
| uuid := uuid.New() | |||||
| c := &CompensateSubStateMachineStateImpl{ | |||||
| ServiceTaskStateImpl: NewServiceTaskStateImpl(), | |||||
| hashcode: uuid.String(), | |||||
| } | |||||
| c.SetType(constant.StateTypeCompensateSubMachine) | |||||
| return c | |||||
| } | |||||
| func (c *CompensateSubStateMachineStateImpl) Hashcode() string { | |||||
| return c.hashcode | |||||
| } | |||||
| func (c *CompensateSubStateMachineStateImpl) SetHashcode(hashcode string) { | |||||
| c.hashcode = hashcode | |||||
| } | |||||
| @@ -0,0 +1,477 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package state | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "reflect" | |||||
| ) | |||||
| type TaskState interface { | |||||
| statelang.State | |||||
| CompensateState() string | |||||
| ForCompensation() bool | |||||
| ForUpdate() bool | |||||
| Retry() []Retry | |||||
| Catches() []ExceptionMatch | |||||
| Status() map[string]string | |||||
| Loop() Loop | |||||
| } | |||||
| type Loop interface { | |||||
| Parallel() int | |||||
| Collection() string | |||||
| ElementVariableName() string | |||||
| ElementIndexName() string | |||||
| CompletionCondition() string | |||||
| } | |||||
| type ExceptionMatch interface { | |||||
| Exceptions() []string | |||||
| // TODO: go dose not support get reflect.Type by string, not use it now | |||||
| ExceptionTypes() []reflect.Type | |||||
| SetExceptionTypes(ExceptionTypes []reflect.Type) | |||||
| Next() string | |||||
| } | |||||
| type Retry interface { | |||||
| Exceptions() []string | |||||
| IntervalSecond() float64 | |||||
| MaxAttempt() int | |||||
| BackoffRate() float64 | |||||
| } | |||||
| type ServiceTaskState interface { | |||||
| TaskState | |||||
| ServiceType() string | |||||
| ServiceName() string | |||||
| ServiceMethod() string | |||||
| ParameterTypes() []string | |||||
| Persist() bool | |||||
| RetryPersistModeUpdate() bool | |||||
| CompensatePersistModeUpdate() bool | |||||
| } | |||||
| type AbstractTaskState struct { | |||||
| *statelang.BaseState | |||||
| loop Loop | |||||
| catches []ExceptionMatch | |||||
| input []interface{} | |||||
| inputExpressions []interface{} | |||||
| output map[string]interface{} | |||||
| outputExpressions map[string]interface{} | |||||
| compensatePersistModeUpdate bool | |||||
| retryPersistModeUpdate bool | |||||
| forCompensation bool | |||||
| forUpdate bool | |||||
| persist bool | |||||
| compensateState string | |||||
| status map[string]string | |||||
| retry []Retry | |||||
| } | |||||
| func NewAbstractTaskState() *AbstractTaskState { | |||||
| return &AbstractTaskState{ | |||||
| BaseState: &statelang.BaseState{}, | |||||
| } | |||||
| } | |||||
| func (a *AbstractTaskState) InputExpressions() []interface{} { | |||||
| return a.inputExpressions | |||||
| } | |||||
| func (a *AbstractTaskState) SetInputExpressions(inputExpressions []interface{}) { | |||||
| a.inputExpressions = inputExpressions | |||||
| } | |||||
| func (a *AbstractTaskState) OutputExpressions() map[string]interface{} { | |||||
| return a.outputExpressions | |||||
| } | |||||
| func (a *AbstractTaskState) SetOutputExpressions(outputExpressions map[string]interface{}) { | |||||
| a.outputExpressions = outputExpressions | |||||
| } | |||||
| func (a *AbstractTaskState) Input() []interface{} { | |||||
| return a.input | |||||
| } | |||||
| func (a *AbstractTaskState) SetInput(input []interface{}) { | |||||
| a.input = input | |||||
| } | |||||
| func (a *AbstractTaskState) Output() map[string]interface{} { | |||||
| return a.output | |||||
| } | |||||
| func (a *AbstractTaskState) SetOutput(output map[string]interface{}) { | |||||
| a.output = output | |||||
| } | |||||
| func (a *AbstractTaskState) CompensatePersistModeUpdate() bool { | |||||
| return a.compensatePersistModeUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) SetCompensatePersistModeUpdate(isCompensatePersistModeUpdate bool) { | |||||
| a.compensatePersistModeUpdate = isCompensatePersistModeUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) RetryPersistModeUpdate() bool { | |||||
| return a.retryPersistModeUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) SetRetryPersistModeUpdate(retryPersistModeUpdate bool) { | |||||
| a.retryPersistModeUpdate = retryPersistModeUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) Persist() bool { | |||||
| return a.persist | |||||
| } | |||||
| func (a *AbstractTaskState) SetPersist(persist bool) { | |||||
| a.persist = persist | |||||
| } | |||||
| func (a *AbstractTaskState) SetLoop(loop Loop) { | |||||
| a.loop = loop | |||||
| } | |||||
| func (a *AbstractTaskState) SetCatches(catches []ExceptionMatch) { | |||||
| a.catches = catches | |||||
| } | |||||
| func (a *AbstractTaskState) SetForCompensation(forCompensation bool) { | |||||
| a.forCompensation = forCompensation | |||||
| } | |||||
| func (a *AbstractTaskState) SetForUpdate(forUpdate bool) { | |||||
| a.forUpdate = forUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) SetCompensateState(compensateState string) { | |||||
| a.compensateState = compensateState | |||||
| } | |||||
| func (a *AbstractTaskState) SetStatus(status map[string]string) { | |||||
| a.status = status | |||||
| } | |||||
| func (a *AbstractTaskState) SetRetry(retry []Retry) { | |||||
| a.retry = retry | |||||
| } | |||||
| func (a *AbstractTaskState) ForCompensation() bool { | |||||
| return a.forCompensation | |||||
| } | |||||
| func (a *AbstractTaskState) ForUpdate() bool { | |||||
| return a.forUpdate | |||||
| } | |||||
| func (a *AbstractTaskState) Catches() []ExceptionMatch { | |||||
| return a.catches | |||||
| } | |||||
| func (a *AbstractTaskState) Loop() Loop { | |||||
| return a.loop | |||||
| } | |||||
| func (a *AbstractTaskState) CompensateState() string { | |||||
| return a.compensateState | |||||
| } | |||||
| func (a *AbstractTaskState) Status() map[string]string { | |||||
| return a.status | |||||
| } | |||||
| func (a *AbstractTaskState) Retry() []Retry { | |||||
| return a.retry | |||||
| } | |||||
| type ServiceTaskStateImpl struct { | |||||
| *AbstractTaskState | |||||
| serviceType string | |||||
| serviceName string | |||||
| serviceMethod string | |||||
| parameterTypes []string | |||||
| method *reflect.Value | |||||
| persist bool | |||||
| retryPersistModeUpdate bool | |||||
| compensatePersistModeUpdate bool | |||||
| isAsync bool | |||||
| } | |||||
| func NewServiceTaskStateImpl() *ServiceTaskStateImpl { | |||||
| return &ServiceTaskStateImpl{ | |||||
| AbstractTaskState: NewAbstractTaskState(), | |||||
| } | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) Method() *reflect.Value { | |||||
| return s.method | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetMethod(method *reflect.Value) { | |||||
| s.method = method | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) IsAsync() bool { | |||||
| return s.isAsync | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetIsAsync(isAsync bool) { | |||||
| s.isAsync = isAsync | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetServiceType(serviceType string) { | |||||
| s.serviceType = serviceType | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetServiceName(serviceName string) { | |||||
| s.serviceName = serviceName | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetServiceMethod(serviceMethod string) { | |||||
| s.serviceMethod = serviceMethod | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetParameterTypes(parameterTypes []string) { | |||||
| s.parameterTypes = parameterTypes | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetPersist(persist bool) { | |||||
| s.persist = persist | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetRetryPersistModeUpdate(retryPersistModeUpdate bool) { | |||||
| s.retryPersistModeUpdate = retryPersistModeUpdate | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) { | |||||
| s.compensatePersistModeUpdate = compensatePersistModeUpdate | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) Loop() Loop { | |||||
| return s.loop | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) ServiceType() string { | |||||
| return s.serviceType | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) ServiceName() string { | |||||
| return s.serviceName | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) ServiceMethod() string { | |||||
| return s.serviceMethod | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) ParameterTypes() []string { | |||||
| return s.parameterTypes | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) Persist() bool { | |||||
| return s.persist | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) RetryPersistModeUpdate() bool { | |||||
| return s.retryPersistModeUpdate | |||||
| } | |||||
| func (s *ServiceTaskStateImpl) CompensatePersistModeUpdate() bool { | |||||
| return s.compensatePersistModeUpdate | |||||
| } | |||||
| type LoopImpl struct { | |||||
| parallel int | |||||
| collection string | |||||
| elementVariableName string | |||||
| elementIndexName string | |||||
| completionCondition string | |||||
| } | |||||
| func (l *LoopImpl) SetParallel(parallel int) { | |||||
| l.parallel = parallel | |||||
| } | |||||
| func (l *LoopImpl) SetCollection(collection string) { | |||||
| l.collection = collection | |||||
| } | |||||
| func (l *LoopImpl) SetElementVariableName(elementVariableName string) { | |||||
| l.elementVariableName = elementVariableName | |||||
| } | |||||
| func (l *LoopImpl) SetElementIndexName(elementIndexName string) { | |||||
| l.elementIndexName = elementIndexName | |||||
| } | |||||
| func (l *LoopImpl) SetCompletionCondition(completionCondition string) { | |||||
| l.completionCondition = completionCondition | |||||
| } | |||||
| func (l *LoopImpl) Parallel() int { | |||||
| return l.parallel | |||||
| } | |||||
| func (l *LoopImpl) Collection() string { | |||||
| return l.collection | |||||
| } | |||||
| func (l *LoopImpl) ElementVariableName() string { | |||||
| return l.elementVariableName | |||||
| } | |||||
| func (l *LoopImpl) ElementIndexName() string { | |||||
| return l.elementIndexName | |||||
| } | |||||
| func (l *LoopImpl) CompletionCondition() string { | |||||
| return l.completionCondition | |||||
| } | |||||
| type RetryImpl struct { | |||||
| exceptions []string | |||||
| intervalSecond float64 | |||||
| maxAttempt int | |||||
| backoffRate float64 | |||||
| } | |||||
| func (r *RetryImpl) SetExceptions(exceptions []string) { | |||||
| r.exceptions = exceptions | |||||
| } | |||||
| func (r *RetryImpl) SetIntervalSecond(intervalSecond float64) { | |||||
| r.intervalSecond = intervalSecond | |||||
| } | |||||
| func (r *RetryImpl) SetMaxAttempt(maxAttempt int) { | |||||
| r.maxAttempt = maxAttempt | |||||
| } | |||||
| func (r *RetryImpl) SetBackoffRate(backoffRate float64) { | |||||
| r.backoffRate = backoffRate | |||||
| } | |||||
| func (r *RetryImpl) Exceptions() []string { | |||||
| return r.exceptions | |||||
| } | |||||
| func (r *RetryImpl) IntervalSecond() float64 { | |||||
| return r.intervalSecond | |||||
| } | |||||
| func (r *RetryImpl) MaxAttempt() int { | |||||
| return r.maxAttempt | |||||
| } | |||||
| func (r *RetryImpl) BackoffRate() float64 { | |||||
| return r.backoffRate | |||||
| } | |||||
| type ExceptionMatchImpl struct { | |||||
| exceptions []string | |||||
| exceptionTypes []reflect.Type | |||||
| next string | |||||
| } | |||||
| func (e *ExceptionMatchImpl) SetExceptions(errors []string) { | |||||
| e.exceptions = errors | |||||
| } | |||||
| func (e *ExceptionMatchImpl) SetNext(next string) { | |||||
| e.next = next | |||||
| } | |||||
| func (e *ExceptionMatchImpl) Exceptions() []string { | |||||
| return e.exceptions | |||||
| } | |||||
| func (e *ExceptionMatchImpl) ExceptionTypes() []reflect.Type { | |||||
| return e.exceptionTypes | |||||
| } | |||||
| func (e *ExceptionMatchImpl) SetExceptionTypes(exceptionTypes []reflect.Type) { | |||||
| e.exceptionTypes = exceptionTypes | |||||
| } | |||||
| func (e *ExceptionMatchImpl) Next() string { | |||||
| return e.next | |||||
| } | |||||
| type ScriptTaskState interface { | |||||
| TaskState | |||||
| ScriptType() string | |||||
| ScriptContent() string | |||||
| } | |||||
| type ScriptTaskStateImpl struct { | |||||
| *AbstractTaskState | |||||
| scriptType string | |||||
| scriptContent string | |||||
| } | |||||
| func NewScriptTaskStateImpl() *ScriptTaskStateImpl { | |||||
| return &ScriptTaskStateImpl{ | |||||
| AbstractTaskState: NewAbstractTaskState(), | |||||
| scriptType: constant.DefaultScriptType, | |||||
| } | |||||
| } | |||||
| func (s *ScriptTaskStateImpl) SetScriptType(scriptType string) { | |||||
| s.scriptType = scriptType | |||||
| } | |||||
| func (s *ScriptTaskStateImpl) SetScriptContent(scriptContent string) { | |||||
| s.scriptContent = scriptContent | |||||
| } | |||||
| func (s *ScriptTaskStateImpl) ScriptType() string { | |||||
| return s.scriptType | |||||
| } | |||||
| func (s *ScriptTaskStateImpl) ScriptContent() string { | |||||
| return s.scriptContent | |||||
| } | |||||
| @@ -0,0 +1,357 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package statelang | |||||
| import "time" | |||||
| type StateInstance interface { | |||||
| ID() string | |||||
| SetID(id string) | |||||
| Name() string | |||||
| SetName(name string) | |||||
| Type() string | |||||
| SetType(typeName string) | |||||
| ServiceName() string | |||||
| SetServiceName(serviceName string) | |||||
| ServiceMethod() string | |||||
| SetServiceMethod(serviceMethod string) | |||||
| ServiceType() string | |||||
| SetServiceType(serviceType string) | |||||
| BusinessKey() string | |||||
| SetBusinessKey(businessKey string) | |||||
| StartedTime() time.Time | |||||
| SetStartedTime(startedTime time.Time) | |||||
| UpdatedTime() time.Time | |||||
| SetUpdatedTime(updateTime time.Time) | |||||
| EndTime() time.Time | |||||
| SetEndTime(endTime time.Time) | |||||
| IsForUpdate() bool | |||||
| SetForUpdate(forUpdate bool) | |||||
| Error() error | |||||
| SetError(err error) | |||||
| InputParams() interface{} | |||||
| SetInputParams(inputParams interface{}) | |||||
| OutputParams() interface{} | |||||
| SetOutputParams(outputParams interface{}) | |||||
| Status() ExecutionStatus | |||||
| SetStatus(status ExecutionStatus) | |||||
| StateIDCompensatedFor() string | |||||
| SetStateIDCompensatedFor(stateIdCompensatedFor string) | |||||
| StateIDRetriedFor() string | |||||
| SetStateIDRetriedFor(stateIdRetriedFor string) | |||||
| CompensationState() StateInstance | |||||
| SetCompensationState(compensationState StateInstance) | |||||
| StateMachineInstance() StateMachineInstance | |||||
| MachineInstanceID() string | |||||
| SetStateMachineInstance(stateMachineInstance StateMachineInstance) | |||||
| IsIgnoreStatus() bool | |||||
| SetIgnoreStatus(ignoreStatus bool) | |||||
| IsForCompensation() bool | |||||
| SerializedInputParams() interface{} | |||||
| SetSerializedInputParams(serializedInputParams interface{}) | |||||
| SerializedOutputParams() interface{} | |||||
| SetSerializedOutputParams(serializedOutputParams interface{}) | |||||
| SerializedError() interface{} | |||||
| SetSerializedError(serializedErr interface{}) | |||||
| CompensationStatus() ExecutionStatus | |||||
| } | |||||
| type StateInstanceImpl struct { | |||||
| id string | |||||
| machineInstanceId string | |||||
| name string | |||||
| typeName string | |||||
| serviceName string | |||||
| serviceMethod string | |||||
| serviceType string | |||||
| businessKey string | |||||
| startedTime time.Time | |||||
| updatedTime time.Time | |||||
| endTime time.Time | |||||
| isForUpdate bool | |||||
| err error | |||||
| serializedErr interface{} | |||||
| inputParams interface{} | |||||
| serializedInputParams interface{} | |||||
| outputParams interface{} | |||||
| serializedOutputParams interface{} | |||||
| status ExecutionStatus | |||||
| stateIdCompensatedFor string | |||||
| stateIdRetriedFor string | |||||
| compensationState StateInstance | |||||
| stateMachineInstance StateMachineInstance | |||||
| ignoreStatus bool | |||||
| } | |||||
| func NewStateInstanceImpl() *StateInstanceImpl { | |||||
| return &StateInstanceImpl{} | |||||
| } | |||||
| func (s *StateInstanceImpl) ID() string { | |||||
| return s.id | |||||
| } | |||||
| func (s *StateInstanceImpl) SetID(id string) { | |||||
| s.id = id | |||||
| } | |||||
| func (s *StateInstanceImpl) MachineInstanceID() string { | |||||
| return s.machineInstanceId | |||||
| } | |||||
| func (s *StateInstanceImpl) SetMachineInstanceID(machineInstanceId string) { | |||||
| s.machineInstanceId = machineInstanceId | |||||
| } | |||||
| func (s *StateInstanceImpl) Name() string { | |||||
| return s.name | |||||
| } | |||||
| func (s *StateInstanceImpl) SetName(name string) { | |||||
| s.name = name | |||||
| } | |||||
| func (s *StateInstanceImpl) Type() string { | |||||
| return s.typeName | |||||
| } | |||||
| func (s *StateInstanceImpl) SetType(typeName string) { | |||||
| s.typeName = typeName | |||||
| } | |||||
| func (s *StateInstanceImpl) ServiceName() string { | |||||
| return s.serviceName | |||||
| } | |||||
| func (s *StateInstanceImpl) SetServiceName(serviceName string) { | |||||
| s.serviceName = serviceName | |||||
| } | |||||
| func (s *StateInstanceImpl) ServiceMethod() string { | |||||
| return s.serviceMethod | |||||
| } | |||||
| func (s *StateInstanceImpl) SetServiceMethod(serviceMethod string) { | |||||
| s.serviceMethod = serviceMethod | |||||
| } | |||||
| func (s *StateInstanceImpl) ServiceType() string { | |||||
| return s.serviceType | |||||
| } | |||||
| func (s *StateInstanceImpl) SetServiceType(serviceType string) { | |||||
| s.serviceType = serviceType | |||||
| } | |||||
| func (s *StateInstanceImpl) BusinessKey() string { | |||||
| return s.businessKey | |||||
| } | |||||
| func (s *StateInstanceImpl) SetBusinessKey(businessKey string) { | |||||
| s.businessKey = businessKey | |||||
| } | |||||
| func (s *StateInstanceImpl) StartedTime() time.Time { | |||||
| return s.startedTime | |||||
| } | |||||
| func (s *StateInstanceImpl) SetStartedTime(startedTime time.Time) { | |||||
| s.startedTime = startedTime | |||||
| } | |||||
| func (s *StateInstanceImpl) UpdatedTime() time.Time { | |||||
| return s.updatedTime | |||||
| } | |||||
| func (s *StateInstanceImpl) SetUpdatedTime(updatedTime time.Time) { | |||||
| s.updatedTime = updatedTime | |||||
| } | |||||
| func (s *StateInstanceImpl) EndTime() time.Time { | |||||
| return s.endTime | |||||
| } | |||||
| func (s *StateInstanceImpl) SetEndTime(endTime time.Time) { | |||||
| s.endTime = endTime | |||||
| } | |||||
| func (s *StateInstanceImpl) IsForUpdate() bool { | |||||
| return s.isForUpdate | |||||
| } | |||||
| func (s *StateInstanceImpl) SetForUpdate(forUpdate bool) { | |||||
| s.isForUpdate = forUpdate | |||||
| } | |||||
| func (s *StateInstanceImpl) Error() error { | |||||
| return s.err | |||||
| } | |||||
| func (s *StateInstanceImpl) SetError(err error) { | |||||
| s.err = err | |||||
| } | |||||
| func (s *StateInstanceImpl) InputParams() interface{} { | |||||
| return s.inputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SetInputParams(inputParams interface{}) { | |||||
| s.inputParams = inputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) OutputParams() interface{} { | |||||
| return s.outputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SetOutputParams(outputParams interface{}) { | |||||
| s.outputParams = outputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) Status() ExecutionStatus { | |||||
| return s.status | |||||
| } | |||||
| func (s *StateInstanceImpl) SetStatus(status ExecutionStatus) { | |||||
| s.status = status | |||||
| } | |||||
| func (s *StateInstanceImpl) StateIDCompensatedFor() string { | |||||
| return s.stateIdCompensatedFor | |||||
| } | |||||
| func (s *StateInstanceImpl) SetStateIDCompensatedFor(stateIdCompensatedFor string) { | |||||
| s.stateIdCompensatedFor = stateIdCompensatedFor | |||||
| } | |||||
| func (s *StateInstanceImpl) StateIDRetriedFor() string { | |||||
| return s.stateIdRetriedFor | |||||
| } | |||||
| func (s *StateInstanceImpl) SetStateIDRetriedFor(stateIdRetriedFor string) { | |||||
| s.stateIdRetriedFor = stateIdRetriedFor | |||||
| } | |||||
| func (s *StateInstanceImpl) CompensationState() StateInstance { | |||||
| return s.compensationState | |||||
| } | |||||
| func (s *StateInstanceImpl) SetCompensationState(compensationState StateInstance) { | |||||
| s.compensationState = compensationState | |||||
| } | |||||
| func (s *StateInstanceImpl) StateMachineInstance() StateMachineInstance { | |||||
| return s.stateMachineInstance | |||||
| } | |||||
| func (s *StateInstanceImpl) SetStateMachineInstance(stateMachineInstance StateMachineInstance) { | |||||
| s.stateMachineInstance = stateMachineInstance | |||||
| } | |||||
| func (s *StateInstanceImpl) IsIgnoreStatus() bool { | |||||
| return s.ignoreStatus | |||||
| } | |||||
| func (s *StateInstanceImpl) SetIgnoreStatus(ignoreStatus bool) { | |||||
| s.ignoreStatus = ignoreStatus | |||||
| } | |||||
| func (s *StateInstanceImpl) IsForCompensation() bool { | |||||
| return s.stateIdCompensatedFor == "" | |||||
| } | |||||
| func (s *StateInstanceImpl) SerializedInputParams() interface{} { | |||||
| return s.serializedInputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SetSerializedInputParams(serializedInputParams interface{}) { | |||||
| s.serializedInputParams = serializedInputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SerializedOutputParams() interface{} { | |||||
| return s.serializedOutputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SetSerializedOutputParams(serializedOutputParams interface{}) { | |||||
| s.serializedOutputParams = serializedOutputParams | |||||
| } | |||||
| func (s *StateInstanceImpl) SerializedError() interface{} { | |||||
| return s.serializedErr | |||||
| } | |||||
| func (s *StateInstanceImpl) SetSerializedError(serializedErr interface{}) { | |||||
| s.serializedErr = serializedErr | |||||
| } | |||||
| func (s *StateInstanceImpl) CompensationStatus() ExecutionStatus { | |||||
| if s.compensationState != nil { | |||||
| return s.compensationState.Status() | |||||
| } | |||||
| //return nil ExecutionStatus | |||||
| var status ExecutionStatus | |||||
| return status | |||||
| } | |||||
| @@ -0,0 +1,278 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package statelang | |||||
| import ( | |||||
| "time" | |||||
| ) | |||||
| type StateMachineStatus string | |||||
| const ( | |||||
| Active StateMachineStatus = "Active" | |||||
| Inactive StateMachineStatus = "Inactive" | |||||
| ) | |||||
| // RecoverStrategy : Recover Strategy | |||||
| type RecoverStrategy string | |||||
| const ( | |||||
| //Compensate stateMachine | |||||
| Compensate RecoverStrategy = "Compensate" | |||||
| // Forward stateMachine | |||||
| Forward RecoverStrategy = "Forward" | |||||
| ) | |||||
| func ValueOfRecoverStrategy(recoverStrategy string) (RecoverStrategy, bool) { | |||||
| switch recoverStrategy { | |||||
| case "Compensate": | |||||
| return Compensate, true | |||||
| case "Forward": | |||||
| return Forward, true | |||||
| default: | |||||
| var recoverStrategy RecoverStrategy | |||||
| return recoverStrategy, false | |||||
| } | |||||
| } | |||||
| type StateMachine interface { | |||||
| ID() string | |||||
| SetID(id string) | |||||
| Name() string | |||||
| SetName(name string) | |||||
| Comment() string | |||||
| SetComment(comment string) | |||||
| StartState() string | |||||
| SetStartState(startState string) | |||||
| Version() string | |||||
| SetVersion(version string) | |||||
| States() map[string]State | |||||
| State(stateName string) State | |||||
| TenantId() string | |||||
| SetTenantId(tenantId string) | |||||
| AppName() string | |||||
| SetAppName(appName string) | |||||
| Type() string | |||||
| SetType(typeName string) | |||||
| Status() StateMachineStatus | |||||
| SetStatus(status StateMachineStatus) | |||||
| RecoverStrategy() RecoverStrategy | |||||
| SetRecoverStrategy(recoverStrategy RecoverStrategy) | |||||
| IsPersist() bool | |||||
| SetPersist(persist bool) | |||||
| IsRetryPersistModeUpdate() bool | |||||
| SetRetryPersistModeUpdate(retryPersistModeUpdate bool) | |||||
| IsCompensatePersistModeUpdate() bool | |||||
| SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) | |||||
| Content() string | |||||
| SetContent(content string) | |||||
| CreateTime() time.Time | |||||
| SetCreateTime(createTime time.Time) | |||||
| } | |||||
| type StateMachineImpl struct { | |||||
| id string | |||||
| tenantId string | |||||
| appName string | |||||
| name string | |||||
| comment string | |||||
| version string | |||||
| startState string | |||||
| status StateMachineStatus | |||||
| recoverStrategy RecoverStrategy | |||||
| persist bool | |||||
| retryPersistModeUpdate bool | |||||
| compensatePersistModeUpdate bool | |||||
| typeName string | |||||
| content string | |||||
| createTime time.Time | |||||
| states map[string]State | |||||
| } | |||||
| func NewStateMachineImpl() *StateMachineImpl { | |||||
| stateMap := make(map[string]State) | |||||
| return &StateMachineImpl{ | |||||
| appName: "SEATA", | |||||
| status: Active, | |||||
| typeName: "STATE_LANG", | |||||
| states: stateMap, | |||||
| } | |||||
| } | |||||
| func (s *StateMachineImpl) ID() string { | |||||
| return s.id | |||||
| } | |||||
| func (s *StateMachineImpl) SetID(id string) { | |||||
| s.id = id | |||||
| } | |||||
| func (s *StateMachineImpl) Name() string { | |||||
| return s.name | |||||
| } | |||||
| func (s *StateMachineImpl) SetName(name string) { | |||||
| s.name = name | |||||
| } | |||||
| func (s *StateMachineImpl) SetComment(comment string) { | |||||
| s.comment = comment | |||||
| } | |||||
| func (s *StateMachineImpl) Comment() string { | |||||
| return s.comment | |||||
| } | |||||
| func (s *StateMachineImpl) StartState() string { | |||||
| return s.startState | |||||
| } | |||||
| func (s *StateMachineImpl) SetStartState(startState string) { | |||||
| s.startState = startState | |||||
| } | |||||
| func (s *StateMachineImpl) Version() string { | |||||
| return s.version | |||||
| } | |||||
| func (s *StateMachineImpl) SetVersion(version string) { | |||||
| s.version = version | |||||
| } | |||||
| func (s *StateMachineImpl) States() map[string]State { | |||||
| return s.states | |||||
| } | |||||
| func (s *StateMachineImpl) State(stateName string) State { | |||||
| if s.states == nil { | |||||
| return nil | |||||
| } | |||||
| return s.states[stateName] | |||||
| } | |||||
| func (s *StateMachineImpl) TenantId() string { | |||||
| return s.tenantId | |||||
| } | |||||
| func (s *StateMachineImpl) SetTenantId(tenantId string) { | |||||
| s.tenantId = tenantId | |||||
| } | |||||
| func (s *StateMachineImpl) AppName() string { | |||||
| return s.appName | |||||
| } | |||||
| func (s *StateMachineImpl) SetAppName(appName string) { | |||||
| s.appName = appName | |||||
| } | |||||
| func (s *StateMachineImpl) Type() string { | |||||
| return s.typeName | |||||
| } | |||||
| func (s *StateMachineImpl) SetType(typeName string) { | |||||
| s.typeName = typeName | |||||
| } | |||||
| func (s *StateMachineImpl) Status() StateMachineStatus { | |||||
| return s.status | |||||
| } | |||||
| func (s *StateMachineImpl) SetStatus(status StateMachineStatus) { | |||||
| s.status = status | |||||
| } | |||||
| func (s *StateMachineImpl) RecoverStrategy() RecoverStrategy { | |||||
| return s.recoverStrategy | |||||
| } | |||||
| func (s *StateMachineImpl) SetRecoverStrategy(recoverStrategy RecoverStrategy) { | |||||
| s.recoverStrategy = recoverStrategy | |||||
| } | |||||
| func (s *StateMachineImpl) IsPersist() bool { | |||||
| return s.persist | |||||
| } | |||||
| func (s *StateMachineImpl) SetPersist(persist bool) { | |||||
| s.persist = persist | |||||
| } | |||||
| func (s *StateMachineImpl) IsRetryPersistModeUpdate() bool { | |||||
| return s.retryPersistModeUpdate | |||||
| } | |||||
| func (s *StateMachineImpl) SetRetryPersistModeUpdate(retryPersistModeUpdate bool) { | |||||
| s.retryPersistModeUpdate = retryPersistModeUpdate | |||||
| } | |||||
| func (s *StateMachineImpl) IsCompensatePersistModeUpdate() bool { | |||||
| return s.compensatePersistModeUpdate | |||||
| } | |||||
| func (s *StateMachineImpl) SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) { | |||||
| s.compensatePersistModeUpdate = compensatePersistModeUpdate | |||||
| } | |||||
| func (s *StateMachineImpl) Content() string { | |||||
| return s.content | |||||
| } | |||||
| func (s *StateMachineImpl) SetContent(content string) { | |||||
| s.content = content | |||||
| } | |||||
| func (s *StateMachineImpl) CreateTime() time.Time { | |||||
| return s.createTime | |||||
| } | |||||
| func (s *StateMachineImpl) SetCreateTime(createTime time.Time) { | |||||
| s.createTime = createTime | |||||
| } | |||||
| @@ -0,0 +1,351 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package statelang | |||||
| import ( | |||||
| "sync" | |||||
| "time" | |||||
| ) | |||||
| type ExecutionStatus string | |||||
| const ( | |||||
| // RU Running | |||||
| RU ExecutionStatus = "RU" | |||||
| // SU Succeed | |||||
| SU ExecutionStatus = "SU" | |||||
| // FA Failed | |||||
| FA ExecutionStatus = "FA" | |||||
| // UN Unknown | |||||
| UN ExecutionStatus = "UN" | |||||
| // SK Skipped | |||||
| SK ExecutionStatus = "SK" | |||||
| ) | |||||
| type StateMachineInstance interface { | |||||
| ID() string | |||||
| SetID(id string) | |||||
| MachineID() string | |||||
| SetMachineID(machineID string) | |||||
| TenantID() string | |||||
| SetTenantID(tenantID string) | |||||
| ParentID() string | |||||
| SetParentID(parentID string) | |||||
| StartedTime() time.Time | |||||
| SetStartedTime(startedTime time.Time) | |||||
| EndTime() time.Time | |||||
| SetEndTime(endTime time.Time) | |||||
| StateList() []StateInstance | |||||
| State(stateId string) StateInstance | |||||
| PutState(stateId string, stateInstance StateInstance) | |||||
| Status() ExecutionStatus | |||||
| SetStatus(status ExecutionStatus) | |||||
| StateMap() map[string]StateInstance | |||||
| SetStateMap(stateMap map[string]StateInstance) | |||||
| CompensationStatus() ExecutionStatus | |||||
| SetCompensationStatus(compensationStatus ExecutionStatus) | |||||
| IsRunning() bool | |||||
| SetRunning(isRunning bool) | |||||
| UpdatedTime() time.Time | |||||
| SetUpdatedTime(updatedTime time.Time) | |||||
| BusinessKey() string | |||||
| SetBusinessKey(businessKey string) | |||||
| Exception() error | |||||
| SetException(err error) | |||||
| StartParams() map[string]interface{} | |||||
| SetStartParams(startParams map[string]interface{}) | |||||
| EndParams() map[string]interface{} | |||||
| SetEndParams(endParams map[string]interface{}) | |||||
| Context() map[string]interface{} | |||||
| PutContext(key string, value interface{}) | |||||
| SetContext(context map[string]interface{}) | |||||
| StateMachine() StateMachine | |||||
| SetStateMachine(stateMachine StateMachine) | |||||
| SerializedStartParams() interface{} | |||||
| SetSerializedStartParams(serializedStartParams interface{}) | |||||
| SerializedEndParams() interface{} | |||||
| SetSerializedEndParams(serializedEndParams interface{}) | |||||
| SerializedError() interface{} | |||||
| SetSerializedError(serializedError interface{}) | |||||
| } | |||||
| type StateMachineInstanceImpl struct { | |||||
| id string | |||||
| machineId string | |||||
| tenantId string | |||||
| parentId string | |||||
| businessKey string | |||||
| startParams map[string]interface{} | |||||
| serializedStartParams interface{} | |||||
| startedTime time.Time | |||||
| endTime time.Time | |||||
| updatedTime time.Time | |||||
| exception error | |||||
| serializedError interface{} | |||||
| endParams map[string]interface{} | |||||
| serializedEndParams interface{} | |||||
| status ExecutionStatus | |||||
| compensationStatus ExecutionStatus | |||||
| isRunning bool | |||||
| context map[string]interface{} | |||||
| stateMachine StateMachine | |||||
| stateList []StateInstance | |||||
| stateMap map[string]StateInstance | |||||
| contextMutex sync.RWMutex // Mutex to protect concurrent access to context | |||||
| stateMutex sync.RWMutex // Mutex to protect concurrent access to stateList and stateMap | |||||
| } | |||||
| func NewStateMachineInstanceImpl() *StateMachineInstanceImpl { | |||||
| return &StateMachineInstanceImpl{ | |||||
| startParams: make(map[string]interface{}), | |||||
| endParams: make(map[string]interface{}), | |||||
| stateList: make([]StateInstance, 0), | |||||
| stateMap: make(map[string]StateInstance)} | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) ID() string { | |||||
| return s.id | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetID(id string) { | |||||
| s.id = id | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) MachineID() string { | |||||
| return s.machineId | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetMachineID(machineID string) { | |||||
| s.machineId = machineID | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) TenantID() string { | |||||
| return s.tenantId | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetTenantID(tenantID string) { | |||||
| s.tenantId = tenantID | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) ParentID() string { | |||||
| return s.parentId | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetParentID(parentID string) { | |||||
| s.parentId = parentID | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) StartedTime() time.Time { | |||||
| return s.startedTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetStartedTime(startedTime time.Time) { | |||||
| s.startedTime = startedTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) EndTime() time.Time { | |||||
| return s.endTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetEndTime(endTime time.Time) { | |||||
| s.endTime = endTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) StateList() []StateInstance { | |||||
| return s.stateList | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) State(stateId string) StateInstance { | |||||
| s.stateMutex.RLock() | |||||
| defer s.stateMutex.RUnlock() | |||||
| return s.stateMap[stateId] | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) PutState(stateId string, stateInstance StateInstance) { | |||||
| s.stateMutex.Lock() | |||||
| defer s.stateMutex.Unlock() | |||||
| stateInstance.SetStateMachineInstance(s) | |||||
| s.stateMap[stateId] = stateInstance | |||||
| s.stateList = append(s.stateList, stateInstance) | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) Status() ExecutionStatus { | |||||
| return s.status | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetStatus(status ExecutionStatus) { | |||||
| s.status = status | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) StateMap() map[string]StateInstance { | |||||
| return s.stateMap | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetStateMap(stateMap map[string]StateInstance) { | |||||
| s.stateMap = stateMap | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) CompensationStatus() ExecutionStatus { | |||||
| return s.compensationStatus | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetCompensationStatus(compensationStatus ExecutionStatus) { | |||||
| s.compensationStatus = compensationStatus | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) IsRunning() bool { | |||||
| return s.isRunning | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetRunning(isRunning bool) { | |||||
| s.isRunning = isRunning | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) UpdatedTime() time.Time { | |||||
| return s.updatedTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetUpdatedTime(updatedTime time.Time) { | |||||
| s.updatedTime = updatedTime | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) BusinessKey() string { | |||||
| return s.businessKey | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetBusinessKey(businessKey string) { | |||||
| s.businessKey = businessKey | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) Exception() error { | |||||
| return s.exception | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetException(err error) { | |||||
| s.exception = err | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) StartParams() map[string]interface{} { | |||||
| return s.startParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetStartParams(startParams map[string]interface{}) { | |||||
| s.startParams = startParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) EndParams() map[string]interface{} { | |||||
| return s.endParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetEndParams(endParams map[string]interface{}) { | |||||
| s.endParams = endParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) Context() map[string]interface{} { | |||||
| return s.context | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) PutContext(key string, value interface{}) { | |||||
| s.contextMutex.Lock() | |||||
| defer s.contextMutex.Unlock() | |||||
| s.context[key] = value | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetContext(context map[string]interface{}) { | |||||
| s.context = context | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) StateMachine() StateMachine { | |||||
| return s.stateMachine | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetStateMachine(stateMachine StateMachine) { | |||||
| s.stateMachine = stateMachine | |||||
| s.machineId = stateMachine.ID() | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SerializedStartParams() interface{} { | |||||
| return s.serializedStartParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetSerializedStartParams(serializedStartParams interface{}) { | |||||
| s.serializedStartParams = serializedStartParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SerializedEndParams() interface{} { | |||||
| return s.serializedEndParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetSerializedEndParams(serializedEndParams interface{}) { | |||||
| s.serializedEndParams = serializedEndParams | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SerializedError() interface{} { | |||||
| return s.serializedError | |||||
| } | |||||
| func (s *StateMachineInstanceImpl) SetSerializedError(serializedError interface{}) { | |||||
| s.serializedError = serializedError | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package statemachine | |||||
| import ( | |||||
| "flag" | |||||
| ) | |||||
| type StateMachineObject struct { | |||||
| Name string `json:"Name" yaml:"Name"` | |||||
| Comment string `json:"Comment" yaml:"Comment"` | |||||
| Version string `json:"Version" yaml:"Version"` | |||||
| StartState string `json:"StartState" yaml:"StartState"` | |||||
| RecoverStrategy string `json:"RecoverStrategy" yaml:"RecoverStrategy"` | |||||
| Persist bool `json:"IsPersist" yaml:"IsPersist"` | |||||
| RetryPersistModeUpdate bool `json:"IsRetryPersistModeUpdate" yaml:"IsRetryPersistModeUpdate"` | |||||
| CompensatePersistModeUpdate bool `json:"IsCompensatePersistModeUpdate" yaml:"IsCompensatePersistModeUpdate"` | |||||
| Type string `json:"Type" yaml:"Type"` | |||||
| States map[string]interface{} `json:"States" yaml:"States"` | |||||
| } | |||||
| func (smo *StateMachineObject) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { | |||||
| f.StringVar(&smo.Name, prefix+".name", "", "State machine name.") | |||||
| f.StringVar(&smo.Comment, prefix+".comment", "", "State machine comment.") | |||||
| f.StringVar(&smo.Version, prefix+".version", "1.0", "State machine version.") | |||||
| f.StringVar(&smo.StartState, prefix+".start-state", "", "State machine start state.") | |||||
| f.StringVar(&smo.RecoverStrategy, prefix+".recover-strategy", "", "State machine recovery strategy.") | |||||
| f.BoolVar(&smo.Persist, prefix+".persist", false, "Whether to persist state machine.") | |||||
| f.BoolVar(&smo.RetryPersistModeUpdate, prefix+".retry-persist-mode-update", false, "Whether to use update mode for retry persistence.") | |||||
| f.BoolVar(&smo.CompensatePersistModeUpdate, prefix+".compensate-persist-mode-update", false, "Whether to use update mode for compensate persistence.") | |||||
| f.StringVar(&smo.Type, prefix+".type", "", "State machine type.") | |||||
| } | |||||
| @@ -0,0 +1,119 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package db | |||||
| import ( | |||||
| "database/sql" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| const TimeLayout = "2006-01-02 15:04:05.999999999-07:00" | |||||
| type ExecStatement[T any] func(obj T, stmt *sql.Stmt) (int64, error) | |||||
| type ScanRows[T any] func(rows *sql.Rows) (T, error) | |||||
| type Store struct { | |||||
| db *sql.DB | |||||
| } | |||||
| func SelectOne[T any](db *sql.DB, sql string, fn ScanRows[T], args ...any) (T, error) { | |||||
| var result T | |||||
| log.Debugf("Preparing SQL: %s", sql) | |||||
| stmt, err := db.Prepare(sql) | |||||
| defer stmt.Close() | |||||
| if err != nil { | |||||
| return result, err | |||||
| } | |||||
| log.Debugf("setting params to Stmt: %v", args) | |||||
| rows, err := stmt.Query(args...) | |||||
| defer rows.Close() | |||||
| if err != nil { | |||||
| return result, nil | |||||
| } | |||||
| if rows.Next() { | |||||
| return fn(rows) | |||||
| } | |||||
| return result, errors.New("no target selected") | |||||
| } | |||||
| func SelectList[T any](db *sql.DB, sql string, fn ScanRows[T], args ...any) ([]T, error) { | |||||
| result := make([]T, 0) | |||||
| log.Debugf("Preparing SQL: %s", sql) | |||||
| stmt, err := db.Prepare(sql) | |||||
| defer stmt.Close() | |||||
| if err != nil { | |||||
| return result, err | |||||
| } | |||||
| log.Debugf("setting params to Stmt: %v", args) | |||||
| rows, err := stmt.Query(args...) | |||||
| defer rows.Close() | |||||
| if err != nil { | |||||
| return result, err | |||||
| } | |||||
| for rows.Next() { | |||||
| obj, err := fn(rows) | |||||
| if err != nil { | |||||
| return result, err | |||||
| } | |||||
| result = append(result, obj) | |||||
| } | |||||
| return result, nil | |||||
| } | |||||
| func ExecuteUpdate[T any](db *sql.DB, sql string, fn ExecStatement[T], obj T) (int64, error) { | |||||
| log.Debugf("Preparing SQL: %s", sql) | |||||
| stmt, err := db.Prepare(sql) | |||||
| defer stmt.Close() | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| log.Debugf("setting params to Stmt: %v", obj) | |||||
| rowsAffected, err := fn(obj, stmt) | |||||
| if err != nil { | |||||
| return rowsAffected, err | |||||
| } | |||||
| return rowsAffected, nil | |||||
| } | |||||
| func ExecuteUpdateArgs(db *sql.DB, sql string, args ...any) (int64, error) { | |||||
| log.Debugf("Preparing SQL: %s", sql) | |||||
| stmt, err := db.Prepare(sql) | |||||
| defer stmt.Close() | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| log.Debugf("setting params to Stmt: %v", args) | |||||
| result, err := stmt.Exec(args...) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| return result.RowsAffected() | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package db | |||||
| import ( | |||||
| "database/sql" | |||||
| "os" | |||||
| "sync" | |||||
| ) | |||||
| var ( | |||||
| oncePrepareDB sync.Once | |||||
| db *sql.DB | |||||
| ) | |||||
| func prepareDB() { | |||||
| oncePrepareDB.Do(func() { | |||||
| var err error | |||||
| db, err = sql.Open("sqlite3", ":memory:") | |||||
| query_, err := os.ReadFile("testdata/sql/saga/sqlite_init.sql") | |||||
| initScript := string(query_) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| if _, err := db.Exec(initScript); err != nil { | |||||
| panic(err) | |||||
| } | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,131 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package db | |||||
| import ( | |||||
| "database/sql" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "regexp" | |||||
| "time" | |||||
| ) | |||||
| const ( | |||||
| StateMachineFields = "id, tenant_id, app_name, name, status, gmt_create, ver, type, content, recover_strategy, comment_" | |||||
| GetStateMachineByIdSql = "SELECT " + StateMachineFields + " FROM ${TABLE_PREFIX}state_machine_def WHERE id = ?" | |||||
| QueryStateMachinesByNameAndTenantSql = "SELECT " + StateMachineFields + " FROM ${TABLE_PREFIX}state_machine_def WHERE name = ? AND tenant_id = ? ORDER BY gmt_create DESC" | |||||
| InsertStateMachineSql = "INSERT INTO ${TABLE_PREFIX}state_machine_def (" + StateMachineFields + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" | |||||
| TablePrefix = "\\$\\{TABLE_PREFIX}" | |||||
| ) | |||||
| type StateLangStore struct { | |||||
| Store | |||||
| tablePrefix string | |||||
| getStateMachineByIdSql string | |||||
| queryStateMachinesByNameAndTenantSql string | |||||
| insertStateMachineSql string | |||||
| } | |||||
| func NewStateLangStore(db *sql.DB, tablePrefix string) *StateLangStore { | |||||
| r := regexp.MustCompile(TablePrefix) | |||||
| stateLangStore := &StateLangStore{ | |||||
| Store: Store{db}, | |||||
| tablePrefix: tablePrefix, | |||||
| getStateMachineByIdSql: r.ReplaceAllString(GetStateMachineByIdSql, tablePrefix), | |||||
| queryStateMachinesByNameAndTenantSql: r.ReplaceAllString(QueryStateMachinesByNameAndTenantSql, tablePrefix), | |||||
| insertStateMachineSql: r.ReplaceAllString(InsertStateMachineSql, tablePrefix), | |||||
| } | |||||
| return stateLangStore | |||||
| } | |||||
| func (s *StateLangStore) GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) { | |||||
| return SelectOne(s.db, s.getStateMachineByIdSql, scanRowsToStateMachine, stateMachineId) | |||||
| } | |||||
| func (s *StateLangStore) GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) { | |||||
| stateMachineList, err := SelectList(s.db, s.queryStateMachinesByNameAndTenantSql, scanRowsToStateMachine, stateMachineName, tenantId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if len(stateMachineList) > 0 { | |||||
| return stateMachineList[0], nil | |||||
| } | |||||
| return nil, nil | |||||
| } | |||||
| func (s *StateLangStore) StoreStateMachine(stateMachine statelang.StateMachine) error { | |||||
| rows, err := ExecuteUpdate(s.db, s.insertStateMachineSql, execStateMachineStatement, stateMachine) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if rows <= 0 { | |||||
| return errors.New("affected rows is smaller than 0") | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func scanRowsToStateMachine(rows *sql.Rows) (statelang.StateMachine, error) { | |||||
| stateMachine := statelang.NewStateMachineImpl() | |||||
| //var id, name, comment, version, appName, content, t, recoverStrategy, tenantId, status string | |||||
| var id, tenantId, appName, name, status, created, version, t, content, recoverStrategy, comment string | |||||
| //var created int64 | |||||
| err := rows.Scan(&id, &tenantId, &appName, &name, &status, &created, &version, &t, &content, &recoverStrategy, &comment) | |||||
| if err != nil { | |||||
| return stateMachine, err | |||||
| } | |||||
| stateMachine.SetID(id) | |||||
| stateMachine.SetName(name) | |||||
| stateMachine.SetComment(comment) | |||||
| stateMachine.SetVersion(version) | |||||
| stateMachine.SetAppName(appName) | |||||
| stateMachine.SetContent(content) | |||||
| createdTime, _ := time.Parse(TimeLayout, created) | |||||
| stateMachine.SetCreateTime(createdTime) | |||||
| stateMachine.SetType(t) | |||||
| if recoverStrategy != "" { | |||||
| stateMachine.SetRecoverStrategy(statelang.RecoverStrategy(recoverStrategy)) | |||||
| } | |||||
| stateMachine.SetTenantId(tenantId) | |||||
| stateMachine.SetStatus(statelang.StateMachineStatus(status)) | |||||
| return stateMachine, nil | |||||
| } | |||||
| func execStateMachineStatement(obj statelang.StateMachine, stmt *sql.Stmt) (int64, error) { | |||||
| result, err := stmt.Exec( | |||||
| obj.ID(), | |||||
| obj.TenantId(), | |||||
| obj.AppName(), | |||||
| obj.Name(), | |||||
| obj.Status(), | |||||
| obj.CreateTime(), | |||||
| obj.Version(), | |||||
| obj.Type(), | |||||
| obj.Content(), | |||||
| obj.RecoverStrategy(), | |||||
| obj.Comment(), | |||||
| ) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| rowsAffected, err := result.RowsAffected() | |||||
| return rowsAffected, err | |||||
| } | |||||
| @@ -0,0 +1,90 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package db | |||||
| import ( | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "testing" | |||||
| "time" | |||||
| _ "github.com/mattn/go-sqlite3" | |||||
| ) | |||||
| func TestStoreAndGetStateMachine(t *testing.T) { | |||||
| prepareDB() | |||||
| stateLangStore := NewStateLangStore(db, "seata_") | |||||
| const stateMachineId = "simpleStateMachine" | |||||
| expected := statelang.NewStateMachineImpl() | |||||
| expected.SetID(stateMachineId) | |||||
| expected.SetName("simpleStateMachine") | |||||
| expected.SetComment("This is a test state machine") | |||||
| expected.SetCreateTime(time.Now()) | |||||
| err := stateLangStore.StoreStateMachine(expected) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| actual, err := stateLangStore.GetStateMachineById(stateMachineId) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| assert.Equal(t, expected.ID(), actual.ID()) | |||||
| assert.Equal(t, expected.Name(), actual.Name()) | |||||
| assert.Equal(t, expected.Comment(), actual.Comment()) | |||||
| assert.Equal(t, expected.CreateTime().UnixNano(), actual.CreateTime().UnixNano()) | |||||
| } | |||||
| func TestStoreAndGetLastVersionStateMachine(t *testing.T) { | |||||
| prepareDB() | |||||
| stateLangStore := NewStateLangStore(db, "seata_") | |||||
| const stateMachineName, tenantId = "simpleStateMachine", "test" | |||||
| stateMachineV1 := statelang.NewStateMachineImpl() | |||||
| stateMachineV1.SetID("simpleStateMachineV1") | |||||
| stateMachineV1.SetName(stateMachineName) | |||||
| stateMachineV1.SetTenantId(tenantId) | |||||
| stateMachineV1.SetCreateTime(time.Now().Add(time.Duration(-1) * time.Millisecond)) | |||||
| stateMachineV2 := statelang.NewStateMachineImpl() | |||||
| stateMachineV2.SetID("simpleStateMachineV2") | |||||
| stateMachineV2.SetName(stateMachineName) | |||||
| stateMachineV2.SetTenantId(tenantId) | |||||
| stateMachineV2.SetCreateTime(time.Now()) | |||||
| err := stateLangStore.StoreStateMachine(stateMachineV1) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| err = stateLangStore.StoreStateMachine(stateMachineV2) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| actual, err := stateLangStore.GetLastVersionStateMachine(stateMachineName, tenantId) | |||||
| if err != nil { | |||||
| t.Error(err) | |||||
| return | |||||
| } | |||||
| assert.Equal(t, stateMachineV2.ID(), actual.ID()) | |||||
| } | |||||
| @@ -0,0 +1,303 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package db | |||||
| import ( | |||||
| "context" | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| func mockProcessContext(stateMachineName string, stateMachineInstance statelang.StateMachineInstance) core.ProcessContext { | |||||
| ctx := core.NewProcessContextBuilder(). | |||||
| WithProcessType(process.StateLang). | |||||
| WithOperationName(constant.OperationNameStart). | |||||
| WithInstruction(core.NewStateInstruction(stateMachineName, "000001")). | |||||
| WithStateMachineInstance(stateMachineInstance). | |||||
| Build() | |||||
| return ctx | |||||
| } | |||||
| func mockMachineInstance(stateMachineName string) statelang.StateMachineInstance { | |||||
| stateMachine := statelang.NewStateMachineImpl() | |||||
| stateMachine.SetName(stateMachineName) | |||||
| stateMachine.SetComment("This is a test state machine") | |||||
| stateMachine.SetCreateTime(time.Now()) | |||||
| inst := statelang.NewStateMachineInstanceImpl() | |||||
| inst.SetStateMachine(stateMachine) | |||||
| inst.SetMachineID(stateMachineName) | |||||
| inst.SetStartParams(map[string]any{"start": 100}) | |||||
| inst.SetStatus(statelang.RU) | |||||
| inst.SetStartedTime(time.Now()) | |||||
| inst.SetUpdatedTime(time.Now()) | |||||
| return inst | |||||
| } | |||||
| func mockStateMachineConfig(context core.ProcessContext) core.StateMachineConfig { | |||||
| cfg := core.NewDefaultStateMachineConfig() | |||||
| context.SetVariable(constant.VarNameStateMachineConfig, cfg) | |||||
| return cfg | |||||
| } | |||||
| func TestStateLogStore_RecordStateMachineStarted(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| expected := mockMachineInstance(stateMachineName) | |||||
| expected.SetBusinessKey("test_started") | |||||
| ctx := mockProcessContext(stateMachineName, expected) | |||||
| mockStateMachineConfig(ctx) | |||||
| err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| actual, err := stateLogStore.GetStateMachineInstance(expected.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, expected.ID(), actual.ID()) | |||||
| assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||||
| assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||||
| assert.Nil(t, actual.Exception()) | |||||
| assert.Nil(t, actual.SerializedError()) | |||||
| assert.Equal(t, expected.Status(), actual.Status()) | |||||
| assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||||
| assert.Equal(t, expected.UpdatedTime().UnixNano(), actual.UpdatedTime().UnixNano()) | |||||
| } | |||||
| func TestStateLogStore_RecordStateMachineFinished(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| expected := mockMachineInstance(stateMachineName) | |||||
| expected.SetBusinessKey("test_finished") | |||||
| ctx := mockProcessContext(stateMachineName, expected) | |||||
| err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| expected.SetEndParams(map[string]any{"end": 100}) | |||||
| expected.SetException(errors.New("this is a test error")) | |||||
| expected.SetStatus(statelang.FA) | |||||
| expected.SetEndTime(time.Now()) | |||||
| expected.SetRunning(false) | |||||
| err = stateLogStore.RecordStateMachineFinished(context.Background(), expected, ctx) | |||||
| assert.Equal(t, "{\"end\":100}", expected.SerializedEndParams()) | |||||
| assert.NotEmpty(t, expected.SerializedError()) | |||||
| actual, err := stateLogStore.GetStateMachineInstance(expected.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, expected.ID(), actual.ID()) | |||||
| assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||||
| assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||||
| assert.Equal(t, "this is a test error", actual.Exception().Error()) | |||||
| assert.Equal(t, expected.Status(), actual.Status()) | |||||
| assert.Equal(t, expected.IsRunning(), actual.IsRunning()) | |||||
| assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||||
| assert.Greater(t, actual.UpdatedTime().UnixNano(), expected.UpdatedTime().UnixNano()) | |||||
| assert.False(t, expected.EndTime().IsZero()) | |||||
| } | |||||
| func TestStateLogStore_RecordStateMachineRestarted(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| expected := mockMachineInstance(stateMachineName) | |||||
| expected.SetBusinessKey("test_restarted") | |||||
| ctx := mockProcessContext(stateMachineName, expected) | |||||
| err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| expected.SetRunning(false) | |||||
| err = stateLogStore.RecordStateMachineFinished(context.Background(), expected, ctx) | |||||
| actual, err := stateLogStore.GetStateMachineInstance(expected.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.False(t, actual.IsRunning()) | |||||
| actual.SetRunning(true) | |||||
| err = stateLogStore.RecordStateMachineRestarted(context.Background(), actual, ctx) | |||||
| assert.Nil(t, err) | |||||
| actual, err = stateLogStore.GetStateMachineInstance(actual.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.True(t, actual.IsRunning()) | |||||
| } | |||||
| func TestStateLogStore_RecordStateStarted(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| machineInstance := mockMachineInstance("stateMachine") | |||||
| ctx := mockProcessContext(stateMachineName, machineInstance) | |||||
| machineInstance.SetID("test") | |||||
| common := statelang.NewStateInstanceImpl() | |||||
| common.SetStateMachineInstance(machineInstance) | |||||
| common.SetMachineInstanceID(machineInstance.ID()) | |||||
| common.SetName("ServiceTask1") | |||||
| common.SetType("ServiceTask") | |||||
| common.SetStartedTime(time.Now()) | |||||
| common.SetServiceName("DemoService") | |||||
| common.SetServiceMethod("foo") | |||||
| common.SetServiceType("RPC") | |||||
| common.SetForUpdate(false) | |||||
| common.SetInputParams(map[string]string{"input": "test"}) | |||||
| common.SetStatus(statelang.RU) | |||||
| common.SetBusinessKey("test_state_started") | |||||
| origin := statelang.NewStateInstanceImpl() | |||||
| origin.SetID("origin") | |||||
| origin.SetStateMachineInstance(machineInstance) | |||||
| origin.SetMachineInstanceID(machineInstance.ID()) | |||||
| machineInstance.PutState("origin", origin) | |||||
| retried := statelang.NewStateInstanceImpl() | |||||
| retried.SetStateMachineInstance(machineInstance) | |||||
| retried.SetMachineInstanceID(machineInstance.ID()) | |||||
| retried.SetID("origin.1") | |||||
| retried.SetStateIDRetriedFor("origin") | |||||
| compensated := statelang.NewStateInstanceImpl() | |||||
| compensated.SetStateMachineInstance(machineInstance) | |||||
| compensated.SetMachineInstanceID(machineInstance.ID()) | |||||
| compensated.SetID("origin-1") | |||||
| compensated.SetStateIDCompensatedFor("origin") | |||||
| tests := []struct { | |||||
| name string | |||||
| expected statelang.StateInstance | |||||
| }{ | |||||
| {"common", common}, | |||||
| {"retried", retried}, | |||||
| {"compensated", compensated}, | |||||
| } | |||||
| for _, test := range tests { | |||||
| t.Run(test.name, func(t *testing.T) { | |||||
| err := stateLogStore.RecordStateStarted(context.Background(), test.expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| actual, err := stateLogStore.GetStateInstance(test.expected.ID(), machineInstance.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, test.expected.ID(), actual.ID()) | |||||
| assert.Equal(t, test.expected.StateMachineInstance().ID(), actual.MachineInstanceID()) | |||||
| assert.Equal(t, test.expected.Name(), actual.Name()) | |||||
| assert.Equal(t, test.expected.Type(), actual.Type()) | |||||
| assert.Equal(t, test.expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||||
| assert.Equal(t, test.expected.ServiceName(), actual.ServiceName()) | |||||
| assert.Equal(t, test.expected.ServiceMethod(), actual.ServiceMethod()) | |||||
| assert.Equal(t, test.expected.ServiceType(), actual.ServiceType()) | |||||
| assert.Equal(t, test.expected.IsForUpdate(), actual.IsForUpdate()) | |||||
| assert.Equal(t, test.expected.SerializedInputParams(), actual.SerializedInputParams()) | |||||
| assert.Equal(t, test.expected.Status(), actual.Status()) | |||||
| assert.Equal(t, test.expected.BusinessKey(), actual.BusinessKey()) | |||||
| assert.Equal(t, test.expected.StateIDCompensatedFor(), actual.StateIDCompensatedFor()) | |||||
| assert.Equal(t, test.expected.StateIDRetriedFor(), actual.StateIDRetriedFor()) | |||||
| }) | |||||
| } | |||||
| } | |||||
| func TestStateLogStore_RecordStateFinished(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| machineInstance := mockMachineInstance("stateMachine") | |||||
| ctx := mockProcessContext(stateMachineName, machineInstance) | |||||
| machineInstance.SetID("test") | |||||
| expected := statelang.NewStateInstanceImpl() | |||||
| expected.SetStateMachineInstance(machineInstance) | |||||
| expected.SetMachineInstanceID(machineInstance.ID()) | |||||
| err := stateLogStore.RecordStateStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| expected.SetStatus(statelang.UN) | |||||
| expected.SetError(errors.New("this is a test error")) | |||||
| expected.SetOutputParams(map[string]string{"output": "test"}) | |||||
| err = stateLogStore.RecordStateFinished(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| actual, err := stateLogStore.GetStateInstance(expected.ID(), machineInstance.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, expected.Status(), actual.Status()) | |||||
| assert.Equal(t, expected.Error().Error(), actual.Error().Error()) | |||||
| assert.NotEmpty(t, actual.OutputParams()) | |||||
| assert.Equal(t, expected.SerializedOutputParams(), actual.SerializedOutputParams()) | |||||
| } | |||||
| func TestStateLogStore_GetStateMachineInstanceByBusinessKey(t *testing.T) { | |||||
| prepareDB() | |||||
| const stateMachineName = "stateMachine" | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| expected := mockMachineInstance(stateMachineName) | |||||
| expected.SetBusinessKey("test_business_key") | |||||
| expected.SetTenantID("000001") | |||||
| ctx := mockProcessContext(stateMachineName, expected) | |||||
| err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| actual, err := stateLogStore.GetStateMachineInstanceByBusinessKey(expected.BusinessKey(), expected.TenantID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, expected.ID(), actual.ID()) | |||||
| assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||||
| assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||||
| assert.Nil(t, actual.Exception()) | |||||
| assert.Nil(t, actual.SerializedError()) | |||||
| assert.Equal(t, expected.Status(), actual.Status()) | |||||
| assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||||
| assert.Equal(t, expected.UpdatedTime().UnixNano(), actual.UpdatedTime().UnixNano()) | |||||
| } | |||||
| func TestStateLogStore_GetStateMachineInstanceByParentId(t *testing.T) { | |||||
| prepareDB() | |||||
| const ( | |||||
| stateMachineName = "stateMachine" | |||||
| parentId = "parent" | |||||
| ) | |||||
| stateLogStore := NewStateLogStore(db, "seata_") | |||||
| expected := mockMachineInstance(stateMachineName) | |||||
| expected.SetBusinessKey("test_parent_id") | |||||
| expected.SetParentID(parentId) | |||||
| ctx := mockProcessContext(stateMachineName, expected) | |||||
| err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||||
| assert.Nil(t, err) | |||||
| actualList, err := stateLogStore.GetStateMachineInstanceByParentId(parentId) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, 1, len(actualList)) | |||||
| actual := actualList[0] | |||||
| assert.Equal(t, expected.ID(), actual.ID()) | |||||
| assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||||
| // no startParams, endParams and Exception | |||||
| assert.NotEqual(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||||
| assert.Nil(t, actual.Exception()) | |||||
| assert.Nil(t, actual.SerializedError()) | |||||
| assert.Equal(t, expected.Status(), actual.Status()) | |||||
| assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||||
| assert.Equal(t, expected.UpdatedTime().UnixNano(), actual.UpdatedTime().UnixNano()) | |||||
| } | |||||
| @@ -0,0 +1,141 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package repository | |||||
| import ( | |||||
| "context" | |||||
| "database/sql" | |||||
| "github.com/pkg/errors" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/store/db" | |||||
| ) | |||||
| var ( | |||||
| stateLogRepositoryImpl *StateLogRepositoryImpl | |||||
| ) | |||||
| type StateLogRepositoryImpl struct { | |||||
| stateLogStore *db.StateLogStore | |||||
| } | |||||
| func NewStateLogRepositoryImpl(hsqldb *sql.DB, tablePrefix string) *StateLogRepositoryImpl { | |||||
| if stateLogRepositoryImpl == nil { | |||||
| stateLogRepositoryImpl = &StateLogRepositoryImpl{ | |||||
| stateLogStore: db.NewStateLogStore(hsqldb, tablePrefix), | |||||
| } | |||||
| } | |||||
| return stateLogRepositoryImpl | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) RecordStateMachineStarted( | |||||
| ctx context.Context, | |||||
| machineInstance statelang.StateMachineInstance, | |||||
| processContext core.ProcessContext, | |||||
| ) error { | |||||
| if s.stateLogStore == nil { | |||||
| return errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.RecordStateMachineStarted(ctx, machineInstance, processContext) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) RecordStateMachineFinished( | |||||
| ctx context.Context, | |||||
| machineInstance statelang.StateMachineInstance, | |||||
| processContext core.ProcessContext, | |||||
| ) error { | |||||
| if s.stateLogStore == nil { | |||||
| return errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.RecordStateMachineFinished(ctx, machineInstance, processContext) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) RecordStateMachineRestarted( | |||||
| ctx context.Context, | |||||
| machineInstance statelang.StateMachineInstance, | |||||
| processContext core.ProcessContext, | |||||
| ) error { | |||||
| if s.stateLogStore == nil { | |||||
| return errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.RecordStateMachineRestarted(ctx, machineInstance, processContext) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) RecordStateStarted( | |||||
| ctx context.Context, | |||||
| stateInstance statelang.StateInstance, | |||||
| processContext core.ProcessContext, | |||||
| ) error { | |||||
| if s.stateLogStore == nil { | |||||
| return errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.RecordStateStarted(ctx, stateInstance, processContext) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) RecordStateFinished( | |||||
| ctx context.Context, | |||||
| stateInstance statelang.StateInstance, | |||||
| processContext core.ProcessContext, | |||||
| ) error { | |||||
| if s.stateLogStore == nil { | |||||
| return errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.RecordStateFinished(ctx, stateInstance, processContext) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateMachineInstance, error) { | |||||
| if s.stateLogStore == nil { | |||||
| return nil, errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.GetStateMachineInstance(stateMachineInstanceId) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) GetStateMachineInstanceByBusinessKey(businessKey, tenantId string) (statelang.StateMachineInstance, error) { | |||||
| if s.stateLogStore == nil { | |||||
| return nil, errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.GetStateMachineInstanceByBusinessKey(businessKey, tenantId) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) QueryStateMachineInstanceByParentId(parentId string) ([]statelang.StateMachineInstance, error) { | |||||
| if s.stateLogStore == nil { | |||||
| return nil, errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.GetStateMachineInstanceByParentId(parentId) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) GetStateInstance(stateInstanceId, machineInstId string) (statelang.StateInstance, error) { | |||||
| if s.stateLogStore == nil { | |||||
| return nil, errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.GetStateInstance(stateInstanceId, machineInstId) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) QueryStateInstanceListByMachineInstanceId(stateMachineInstanceId string) ([]statelang.StateInstance, error) { | |||||
| if s.stateLogStore == nil { | |||||
| return nil, errors.New("stateLogStore is not initialized") | |||||
| } | |||||
| return s.stateLogStore.GetStateInstanceListByMachineInstanceId(stateMachineInstanceId) | |||||
| } | |||||
| func (s *StateLogRepositoryImpl) SetStateLogStore(stateLogStore *db.StateLogStore) { | |||||
| s.stateLogStore = stateLogStore | |||||
| } | |||||
| @@ -0,0 +1,237 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package repository | |||||
| import ( | |||||
| "io" | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/parser" | |||||
| "github.com/seata/seata-go/pkg/util/log" | |||||
| ) | |||||
| const ( | |||||
| DefaultJsonParser = "fastjson" | |||||
| ) | |||||
| var ( | |||||
| stateMachineRepositoryImpl *StateMachineRepositoryImpl | |||||
| onceStateMachineRepositoryImpl sync.Once | |||||
| ) | |||||
| type StateMachineRepositoryImpl struct { | |||||
| stateMachineMapById map[string]statelang.StateMachine | |||||
| stateMachineMapByNameAndTenant map[string]statelang.StateMachine | |||||
| stateLangStore core.StateLangStore | |||||
| seqGenerator sequence.SeqGenerator | |||||
| defaultTenantId string | |||||
| jsonParserName string | |||||
| charset string | |||||
| mutex *sync.Mutex | |||||
| } | |||||
| func GetStateMachineRepositoryImpl() *StateMachineRepositoryImpl { | |||||
| if stateMachineRepositoryImpl == nil { | |||||
| onceStateMachineRepositoryImpl.Do(func() { | |||||
| //TODO charset is not use | |||||
| //TODO using json parser | |||||
| stateMachineRepositoryImpl = &StateMachineRepositoryImpl{ | |||||
| stateMachineMapById: make(map[string]statelang.StateMachine), | |||||
| stateMachineMapByNameAndTenant: make(map[string]statelang.StateMachine), | |||||
| seqGenerator: sequence.NewUUIDSeqGenerator(), | |||||
| jsonParserName: DefaultJsonParser, | |||||
| charset: "UTF-8", | |||||
| mutex: &sync.Mutex{}, | |||||
| } | |||||
| }) | |||||
| } | |||||
| return stateMachineRepositoryImpl | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) { | |||||
| stateMachine := s.stateMachineMapById[stateMachineId] | |||||
| if stateMachine == nil && s.stateLangStore != nil { | |||||
| s.mutex.Lock() | |||||
| defer s.mutex.Unlock() | |||||
| stateMachine = s.stateMachineMapById[stateMachineId] | |||||
| if stateMachine == nil { | |||||
| oldStateMachine, err := s.stateLangStore.GetStateMachineById(stateMachineId) | |||||
| if err != nil { | |||||
| return oldStateMachine, err | |||||
| } | |||||
| parseStatMachine, err := parser.NewJSONStateMachineParser().Parse(oldStateMachine.Content()) | |||||
| if err != nil { | |||||
| return oldStateMachine, err | |||||
| } | |||||
| oldStateMachine.SetStartState(parseStatMachine.StartState()) | |||||
| for key, val := range parseStatMachine.States() { | |||||
| oldStateMachine.States()[key] = val | |||||
| } | |||||
| s.stateMachineMapById[stateMachineId] = oldStateMachine | |||||
| s.stateMachineMapByNameAndTenant[oldStateMachine.Name()+"_"+oldStateMachine.TenantId()] = oldStateMachine | |||||
| return oldStateMachine, nil | |||||
| } | |||||
| } | |||||
| return stateMachine, nil | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetStateMachineByNameAndTenantId(stateMachineName string, tenantId string) (statelang.StateMachine, error) { | |||||
| return s.GetLastVersionStateMachine(stateMachineName, tenantId) | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) { | |||||
| key := stateMachineName + "_" + tenantId | |||||
| stateMachine := s.stateMachineMapByNameAndTenant[key] | |||||
| if stateMachine == nil && s.stateLangStore != nil { | |||||
| s.mutex.Lock() | |||||
| defer s.mutex.Unlock() | |||||
| stateMachine = s.stateMachineMapById[key] | |||||
| if stateMachine == nil { | |||||
| oldStateMachine, err := s.stateLangStore.GetLastVersionStateMachine(stateMachineName, tenantId) | |||||
| if err != nil { | |||||
| return oldStateMachine, err | |||||
| } | |||||
| parseStatMachine, err := parser.NewJSONStateMachineParser().Parse(oldStateMachine.Content()) | |||||
| if err != nil { | |||||
| return oldStateMachine, err | |||||
| } | |||||
| oldStateMachine.SetStartState(parseStatMachine.StartState()) | |||||
| for key, val := range parseStatMachine.States() { | |||||
| oldStateMachine.States()[key] = val | |||||
| } | |||||
| s.stateMachineMapById[oldStateMachine.ID()] = oldStateMachine | |||||
| s.stateMachineMapByNameAndTenant[key] = oldStateMachine | |||||
| return oldStateMachine, nil | |||||
| } | |||||
| } | |||||
| return stateMachine, nil | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) RegistryStateMachine(machine statelang.StateMachine) error { | |||||
| stateMachineName := machine.Name() | |||||
| tenantId := machine.TenantId() | |||||
| if s.stateLangStore != nil { | |||||
| oldStateMachine, err := s.stateLangStore.GetLastVersionStateMachine(stateMachineName, tenantId) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if oldStateMachine != nil { | |||||
| if oldStateMachine.Content() == machine.Content() && machine.Version() != "" && machine.Version() == oldStateMachine.Version() { | |||||
| log.Debugf("StateMachine[%s] is already exist a same version", stateMachineName) | |||||
| machine.SetID(oldStateMachine.ID()) | |||||
| machine.SetCreateTime(oldStateMachine.CreateTime()) | |||||
| s.stateMachineMapById[machine.ID()] = machine | |||||
| s.stateMachineMapByNameAndTenant[machine.Name()+"_"+machine.TenantId()] = machine | |||||
| return nil | |||||
| } | |||||
| } | |||||
| if machine.ID() == "" { | |||||
| machine.SetID(s.seqGenerator.GenerateId(constant.SeqEntityStateMachine, "")) | |||||
| } | |||||
| machine.SetCreateTime(time.Now()) | |||||
| err = s.stateLangStore.StoreStateMachine(machine) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| if machine.ID() == "" { | |||||
| machine.SetID(s.seqGenerator.GenerateId(constant.SeqEntityStateMachine, "")) | |||||
| } | |||||
| s.stateMachineMapById[machine.ID()] = machine | |||||
| s.stateMachineMapByNameAndTenant[machine.Name()+"_"+machine.TenantId()] = machine | |||||
| return nil | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) RegistryStateMachineByReader(reader io.Reader) error { | |||||
| jsonByte, err := io.ReadAll(reader) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| json := string(jsonByte) | |||||
| parseStatMachine, err := parser.NewJSONStateMachineParser().Parse(json) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if parseStatMachine == nil { | |||||
| return nil | |||||
| } | |||||
| parseStatMachine.SetContent(json) | |||||
| s.RegistryStateMachine(parseStatMachine) | |||||
| log.Debugf("===== StateMachine Loaded: %s", json) | |||||
| return nil | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) SetStateLangStore(stateLangStore core.StateLangStore) { | |||||
| s.stateLangStore = stateLangStore | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) SetSeqGenerator(seqGenerator sequence.SeqGenerator) { | |||||
| s.seqGenerator = seqGenerator | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) SetCharset(charset string) { | |||||
| s.charset = charset | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetCharset() string { | |||||
| return s.charset | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) SetDefaultTenantId(defaultTenantId string) { | |||||
| s.defaultTenantId = defaultTenantId | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetDefaultTenantId() string { | |||||
| return s.defaultTenantId | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) SetJsonParserName(jsonParserName string) { | |||||
| s.jsonParserName = jsonParserName | |||||
| } | |||||
| func (s *StateMachineRepositoryImpl) GetJsonParserName() string { | |||||
| return s.jsonParserName | |||||
| } | |||||
| @@ -0,0 +1,118 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package repository | |||||
| import ( | |||||
| "database/sql" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/parser" | |||||
| "os" | |||||
| "sync" | |||||
| "testing" | |||||
| "time" | |||||
| _ "github.com/mattn/go-sqlite3" | |||||
| "github.com/stretchr/testify/assert" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||||
| "github.com/seata/seata-go/pkg/saga/statemachine/store/db" | |||||
| ) | |||||
| var ( | |||||
| oncePrepareDB sync.Once | |||||
| testdb *sql.DB | |||||
| ) | |||||
| func prepareDB() { | |||||
| oncePrepareDB.Do(func() { | |||||
| var err error | |||||
| testdb, err = sql.Open("sqlite3", ":memory:") | |||||
| query_, err := os.ReadFile("../../../../../testdata/sql/saga/sqlite_init.sql") | |||||
| initScript := string(query_) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| if _, err := testdb.Exec(initScript); err != nil { | |||||
| panic(err) | |||||
| } | |||||
| }) | |||||
| } | |||||
| func loadStateMachineByYaml() string { | |||||
| query, _ := os.ReadFile("../../../../../testdata/saga/statelang/simple_statemachine.json") | |||||
| return string(query) | |||||
| } | |||||
| func TestStateMachineInMemory(t *testing.T) { | |||||
| const stateMachineId, stateMachineName, tenantId = "simpleStateMachine", "simpleStateMachine", "test" | |||||
| stateMachine := statelang.NewStateMachineImpl() | |||||
| stateMachine.SetID(stateMachineId) | |||||
| stateMachine.SetName(stateMachineName) | |||||
| stateMachine.SetTenantId(tenantId) | |||||
| stateMachine.SetComment("This is a test state machine") | |||||
| stateMachine.SetCreateTime(time.Now()) | |||||
| repository := GetStateMachineRepositoryImpl() | |||||
| err := repository.RegistryStateMachine(stateMachine) | |||||
| assert.Nil(t, err) | |||||
| machineById, err := repository.GetStateMachineById(stateMachine.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, stateMachine.Name(), machineById.Name()) | |||||
| assert.Equal(t, stateMachine.TenantId(), machineById.TenantId()) | |||||
| assert.Equal(t, stateMachine.Comment(), machineById.Comment()) | |||||
| assert.Equal(t, stateMachine.CreateTime().UnixNano(), machineById.CreateTime().UnixNano()) | |||||
| machineByNameAndTenantId, err := repository.GetLastVersionStateMachine(stateMachine.Name(), stateMachine.TenantId()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, stateMachine.ID(), machineByNameAndTenantId.ID()) | |||||
| assert.Equal(t, stateMachine.Comment(), machineById.Comment()) | |||||
| assert.Equal(t, stateMachine.CreateTime().UnixNano(), machineById.CreateTime().UnixNano()) | |||||
| } | |||||
| func TestStateMachineInDb(t *testing.T) { | |||||
| prepareDB() | |||||
| const tenantId = "test" | |||||
| yaml := loadStateMachineByYaml() | |||||
| stateMachine, err := parser.NewJSONStateMachineParser().Parse(yaml) | |||||
| assert.Nil(t, err) | |||||
| stateMachine.SetTenantId(tenantId) | |||||
| stateMachine.SetContent(yaml) | |||||
| repository := GetStateMachineRepositoryImpl() | |||||
| repository.SetStateLangStore(db.NewStateLangStore(testdb, "seata_")) | |||||
| err = repository.RegistryStateMachine(stateMachine) | |||||
| assert.Nil(t, err) | |||||
| repository.stateMachineMapById[stateMachine.ID()] = nil | |||||
| machineById, err := repository.GetStateMachineById(stateMachine.ID()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, stateMachine.Name(), machineById.Name()) | |||||
| assert.Equal(t, stateMachine.TenantId(), machineById.TenantId()) | |||||
| assert.Equal(t, stateMachine.Comment(), machineById.Comment()) | |||||
| assert.Equal(t, stateMachine.CreateTime().UnixNano(), machineById.CreateTime().UnixNano()) | |||||
| repository.stateMachineMapByNameAndTenant[stateMachine.Name()+"_"+stateMachine.TenantId()] = nil | |||||
| machineByNameAndTenantId, err := repository.GetLastVersionStateMachine(stateMachine.Name(), stateMachine.TenantId()) | |||||
| assert.Nil(t, err) | |||||
| assert.Equal(t, stateMachine.ID(), machineByNameAndTenantId.ID()) | |||||
| assert.Equal(t, stateMachine.Comment(), machineById.Comment()) | |||||
| assert.Equal(t, stateMachine.CreateTime().UnixNano(), machineById.CreateTime().UnixNano()) | |||||
| } | |||||
| @@ -151,3 +151,28 @@ func (g *GlobalTransactionManager) Rollback(ctx context.Context, gtr *GlobalTran | |||||
| return nil | return nil | ||||
| } | } | ||||
| // GlobalReport Global report. | |||||
| func (g *GlobalTransactionManager) GlobalReport(ctx context.Context, gtr *GlobalTransaction) (message.GlobalStatus, error) { | |||||
| if gtr.Xid == "" { | |||||
| return message.GlobalStatusUnKnown, fmt.Errorf("GlobalReport xid should not be empty") | |||||
| } | |||||
| req := message.GlobalReportRequest{ | |||||
| AbstractGlobalEndRequest: message.AbstractGlobalEndRequest{ | |||||
| Xid: gtr.Xid, | |||||
| }, | |||||
| GlobalStatus: gtr.TxStatus, | |||||
| } | |||||
| res, err := getty.GetGettyRemotingClient().SendSyncRequest(req) | |||||
| if err != nil { | |||||
| log.Errorf("GlobalBeginRequest error %v", err) | |||||
| return message.GlobalStatusUnKnown, err | |||||
| } | |||||
| if res == nil || res.(message.GlobalReportResponse).ResultCode == message.ResultCodeFailed { | |||||
| log.Errorf("GlobalReportRequest result is empty or result code is failed, res %v", res) | |||||
| return message.GlobalStatusUnKnown, fmt.Errorf("GlobalReportRequest result is empty or result code is failed.") | |||||
| } | |||||
| log.Infof("GlobalReportRequest success, res %v", res) | |||||
| return res.(message.GlobalReportResponse).GlobalStatus, nil | |||||
| } | |||||
| @@ -361,7 +361,7 @@ func TestBeginNewGtx(t *testing.T) { | |||||
| assert.Equal(t, message.GlobalStatusBegin, *GetTxStatus(ctx)) | assert.Equal(t, message.GlobalStatusBegin, *GetTxStatus(ctx)) | ||||
| // case return error | // case return error | ||||
| err := errors.New("Mock Error") | |||||
| err := errors.New("Mock Exception") | |||||
| gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), "Begin", | gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), "Begin", | ||||
| func(_ *GlobalTransactionManager, ctx context.Context, timeout time.Duration) error { | func(_ *GlobalTransactionManager, ctx context.Context, timeout time.Duration) error { | ||||
| return err | return err | ||||
| @@ -17,7 +17,10 @@ | |||||
| package collection | package collection | ||||
| import "strings" | |||||
| import ( | |||||
| "container/list" | |||||
| "strings" | |||||
| ) | |||||
| const ( | const ( | ||||
| KvSplit = "=" | KvSplit = "=" | ||||
| @@ -81,3 +84,42 @@ func DecodeMap(data []byte) map[string]string { | |||||
| return ctxMap | return ctxMap | ||||
| } | } | ||||
| type Stack struct { | |||||
| list *list.List | |||||
| } | |||||
| func NewStack() *Stack { | |||||
| list := list.New() | |||||
| return &Stack{list} | |||||
| } | |||||
| func (stack *Stack) Push(value interface{}) { | |||||
| stack.list.PushBack(value) | |||||
| } | |||||
| func (stack *Stack) Pop() interface{} { | |||||
| e := stack.list.Back() | |||||
| if e != nil { | |||||
| stack.list.Remove(e) | |||||
| return e.Value | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (stack *Stack) Peak() interface{} { | |||||
| e := stack.list.Back() | |||||
| if e != nil { | |||||
| return e.Value | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (stack *Stack) Len() int { | |||||
| return stack.list.Len() | |||||
| } | |||||
| func (stack *Stack) Empty() bool { | |||||
| return stack.list.Len() == 0 | |||||
| } | |||||
| @@ -57,3 +57,26 @@ func TestEncodeDecodeMap(t *testing.T) { | |||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| func TestStack(t *testing.T) { | |||||
| stack := NewStack() | |||||
| stack.Push(1) | |||||
| stack.Push(2) | |||||
| stack.Push(3) | |||||
| stack.Push(4) | |||||
| len := stack.Len() | |||||
| if len != 4 { | |||||
| t.Errorf("stack.Len() failed. Got %d, expected 4.", len) | |||||
| } | |||||
| value := stack.Peak().(int) | |||||
| if value != 4 { | |||||
| t.Errorf("stack.Peak() failed. Got %d, expected 4.", value) | |||||
| } | |||||
| value = stack.Pop().(int) | |||||
| if value != 4 { | |||||
| t.Errorf("stack.Pop() failed. Got %d, expected 4.", value) | |||||
| } | |||||
| } | |||||
| @@ -100,4 +100,17 @@ const ( | |||||
| // FencePhaseError have fence phase but is not illegal value | // FencePhaseError have fence phase but is not illegal value | ||||
| FencePhaseError | FencePhaseError | ||||
| // ObjectNotExists object not exists | |||||
| ObjectNotExists | |||||
| // StateMachineInstanceNotExists State machine instance not exists | |||||
| StateMachineInstanceNotExists | |||||
| // ContextVariableReplayFailed Context variable replay failed | |||||
| ContextVariableReplayFailed | |||||
| // InvalidParameter Context variable replay failed | |||||
| InvalidParameter | |||||
| // OperationDenied Operation denied | |||||
| OperationDenied | |||||
| // ForwardInvalid Forward invalid | |||||
| ForwardInvalid | |||||
| ) | ) | ||||
| @@ -0,0 +1,164 @@ | |||||
| /* | |||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | |||||
| * contributor license agreements. See the NOTICE file distributed with | |||||
| * this work for additional information regarding copyright ownership. | |||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
| * (the "License"); you may not use this file except in compliance with | |||||
| * the License. You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package reflectx | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/pkg/errors" | |||||
| "reflect" | |||||
| "unicode" | |||||
| ) | |||||
| // MapToStruct some state can use this util to parse | |||||
| // TODO 性能测试,性能差的话,直接去解析,不使用反射 | |||||
| func MapToStruct(stateName string, obj interface{}, stateMap map[string]interface{}) error { | |||||
| objVal := reflect.ValueOf(obj) | |||||
| if objVal.Kind() != reflect.Pointer { | |||||
| return errors.New(fmt.Sprintf("State [%s] value required a pointer", stateName)) | |||||
| } | |||||
| structValue := objVal.Elem() | |||||
| if structValue.Kind() != reflect.Struct { | |||||
| return errors.New(fmt.Sprintf("State [%s] value elem required a struct", stateName)) | |||||
| } | |||||
| structType := structValue.Type() | |||||
| for key, value := range stateMap { | |||||
| //Get field, get alias first | |||||
| field, found := getField(structType, key) | |||||
| if !found { | |||||
| continue | |||||
| } | |||||
| fieldVal := structValue.FieldByName(field.Name) | |||||
| if !fieldVal.IsValid() { | |||||
| return errors.New(fmt.Sprintf("State [%s] not support [%s] filed", stateName, key)) | |||||
| } | |||||
| //Get setMethod | |||||
| var setMethod reflect.Value | |||||
| if !fieldVal.CanSet() { | |||||
| setMethod = getFiledSetMethod(field.Name, objVal) | |||||
| if !setMethod.IsValid() { | |||||
| fieldAliasName := field.Tag.Get("alias") | |||||
| setMethod = getFiledSetMethod(fieldAliasName, objVal) | |||||
| } | |||||
| if !setMethod.IsValid() { | |||||
| return errors.New(fmt.Sprintf("State [%s] [%s] field not support setMethod", stateName, key)) | |||||
| } | |||||
| setMethodType := setMethod.Type() | |||||
| if !(setMethodType.NumIn() == 1 && setMethodType.In(0) == fieldVal.Type()) { | |||||
| return errors.New(fmt.Sprintf("State [%s] [%s] field setMethod illegal", stateName, key)) | |||||
| } | |||||
| } | |||||
| val := reflect.ValueOf(value) | |||||
| if fieldVal.Kind() == reflect.Struct { | |||||
| //map[string]interface{} | |||||
| if val.Kind() != reflect.Map { | |||||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) | |||||
| } | |||||
| err := MapToStruct(stateName, fieldVal.Addr().Interface(), value.(map[string]interface{})) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } else if fieldVal.Kind() == reflect.Slice { | |||||
| if val.Kind() != reflect.Slice { | |||||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required slice", stateName, key)) | |||||
| } | |||||
| sliceType := fieldVal.Type().Elem() | |||||
| newSlice := reflect.MakeSlice(fieldVal.Type(), 0, val.Len()) | |||||
| for i := 0; i < val.Len(); i++ { | |||||
| newElem := reflect.New(sliceType.Elem()) | |||||
| elemMap := val.Index(i).Interface().(map[string]interface{}) | |||||
| err := MapToStruct(stateName, newElem.Interface(), elemMap) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| reflect.Append(newSlice, newElem.Elem()) | |||||
| } | |||||
| setFiled(fieldVal, setMethod, newSlice) | |||||
| } else if fieldVal.Kind() == reflect.Map { | |||||
| if val.Kind() != reflect.Map { | |||||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) | |||||
| } | |||||
| mapType := field.Type | |||||
| newMap := reflect.MakeMap(mapType) | |||||
| for _, key := range val.MapKeys() { | |||||
| newVal := reflect.New(mapType.Elem().Elem()) | |||||
| elemMap := val.MapIndex(key).Interface().(map[string]interface{}) | |||||
| err := MapToStruct(stateName, newVal.Interface(), elemMap) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| newMap.SetMapIndex(key, newVal.Elem()) | |||||
| } | |||||
| setFiled(fieldVal, setMethod, newMap) | |||||
| } else { | |||||
| setFiled(fieldVal, setMethod, val) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func getField(t reflect.Type, name string) (reflect.StructField, bool) { | |||||
| for i := 0; i < t.NumField(); i++ { | |||||
| field := t.Field(i) | |||||
| tag, hasAliasTag := field.Tag.Lookup("alias") | |||||
| if (hasAliasTag && tag == name) || (!hasAliasTag && field.Name == name) { | |||||
| return field, true | |||||
| } | |||||
| if field.Anonymous { | |||||
| embeddedField, ok := getField(field.Type, name) | |||||
| if ok { | |||||
| return embeddedField, true | |||||
| } | |||||
| } | |||||
| } | |||||
| return reflect.StructField{}, false | |||||
| } | |||||
| func getFiledSetMethod(name string, structValue reflect.Value) reflect.Value { | |||||
| fieldNameSlice := []rune(name) | |||||
| fieldNameSlice[0] = unicode.ToUpper(fieldNameSlice[0]) | |||||
| setMethodName := "Set" + string(fieldNameSlice) | |||||
| setMethod := structValue.MethodByName(setMethodName) | |||||
| return setMethod | |||||
| } | |||||
| func setFiled(fieldVal reflect.Value, setMethod reflect.Value, val reflect.Value) { | |||||
| if !fieldVal.CanSet() { | |||||
| setMethod.Call([]reflect.Value{ | |||||
| val, | |||||
| }) | |||||
| } else { | |||||
| fieldVal.Set(val) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package product | |||||
| import ( | |||||
| "context" | |||||
| "log" | |||||
| "net" | |||||
| "google.golang.org/grpc" | |||||
| ) | |||||
| type server struct { | |||||
| UnimplementedProductInfoServer | |||||
| } | |||||
| func (*server) AddProduct(context.Context, *Product) (*ProductId, error) { | |||||
| log.Println("add product success") | |||||
| return &ProductId{Value: "1"}, nil | |||||
| } | |||||
| func (*server) GetProduct(context.Context, *ProductId) (*Product, error) { | |||||
| log.Println("get product success") | |||||
| return &Product{Id: "1"}, nil | |||||
| } | |||||
| func StartProductServer() { | |||||
| lis, err := net.Listen("tcp", ":8080") | |||||
| if err != nil { | |||||
| log.Fatalf("failed to listen: %v", err) | |||||
| } | |||||
| s := grpc.NewServer() | |||||
| RegisterProductInfoServer(s, &server{}) | |||||
| log.Printf("server listening at %v", lis.Addr()) | |||||
| if err := s.Serve(lis); err != nil { | |||||
| log.Fatalf("failed to serve: %v", err) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,219 @@ | |||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | |||||
| // versions: | |||||
| // protoc-gen-go v1.32.0 | |||||
| // protoc v4.25.3 | |||||
| // source: product.proto | |||||
| package product | |||||
| import ( | |||||
| protoreflect "google.golang.org/protobuf/reflect/protoreflect" | |||||
| protoimpl "google.golang.org/protobuf/runtime/protoimpl" | |||||
| reflect "reflect" | |||||
| sync "sync" | |||||
| ) | |||||
| const ( | |||||
| // Verify that this generated code is sufficiently up-to-date. | |||||
| _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | |||||
| // Verify that runtime/protoimpl is sufficiently up-to-date. | |||||
| _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | |||||
| ) | |||||
| type Product struct { | |||||
| state protoimpl.MessageState | |||||
| sizeCache protoimpl.SizeCache | |||||
| unknownFields protoimpl.UnknownFields | |||||
| Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` | |||||
| } | |||||
| func (x *Product) Reset() { | |||||
| *x = Product{} | |||||
| if protoimpl.UnsafeEnabled { | |||||
| mi := &file_product_proto_msgTypes[0] | |||||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||||
| ms.StoreMessageInfo(mi) | |||||
| } | |||||
| } | |||||
| func (x *Product) String() string { | |||||
| return protoimpl.X.MessageStringOf(x) | |||||
| } | |||||
| func (*Product) ProtoMessage() {} | |||||
| func (x *Product) ProtoReflect() protoreflect.Message { | |||||
| mi := &file_product_proto_msgTypes[0] | |||||
| if protoimpl.UnsafeEnabled && x != nil { | |||||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||||
| if ms.LoadMessageInfo() == nil { | |||||
| ms.StoreMessageInfo(mi) | |||||
| } | |||||
| return ms | |||||
| } | |||||
| return mi.MessageOf(x) | |||||
| } | |||||
| // Deprecated: Use Product.ProtoReflect.Descriptor instead. | |||||
| func (*Product) Descriptor() ([]byte, []int) { | |||||
| return file_product_proto_rawDescGZIP(), []int{0} | |||||
| } | |||||
| func (x *Product) GetId() string { | |||||
| if x != nil { | |||||
| return x.Id | |||||
| } | |||||
| return "" | |||||
| } | |||||
| type ProductId struct { | |||||
| state protoimpl.MessageState | |||||
| sizeCache protoimpl.SizeCache | |||||
| unknownFields protoimpl.UnknownFields | |||||
| Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` | |||||
| } | |||||
| func (x *ProductId) Reset() { | |||||
| *x = ProductId{} | |||||
| if protoimpl.UnsafeEnabled { | |||||
| mi := &file_product_proto_msgTypes[1] | |||||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||||
| ms.StoreMessageInfo(mi) | |||||
| } | |||||
| } | |||||
| func (x *ProductId) String() string { | |||||
| return protoimpl.X.MessageStringOf(x) | |||||
| } | |||||
| func (*ProductId) ProtoMessage() {} | |||||
| func (x *ProductId) ProtoReflect() protoreflect.Message { | |||||
| mi := &file_product_proto_msgTypes[1] | |||||
| if protoimpl.UnsafeEnabled && x != nil { | |||||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||||
| if ms.LoadMessageInfo() == nil { | |||||
| ms.StoreMessageInfo(mi) | |||||
| } | |||||
| return ms | |||||
| } | |||||
| return mi.MessageOf(x) | |||||
| } | |||||
| // Deprecated: Use ProductId.ProtoReflect.Descriptor instead. | |||||
| func (*ProductId) Descriptor() ([]byte, []int) { | |||||
| return file_product_proto_rawDescGZIP(), []int{1} | |||||
| } | |||||
| func (x *ProductId) GetValue() string { | |||||
| if x != nil { | |||||
| return x.Value | |||||
| } | |||||
| return "" | |||||
| } | |||||
| var File_product_proto protoreflect.FileDescriptor | |||||
| var file_product_proto_rawDesc = []byte{ | |||||
| 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, | |||||
| 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x19, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, | |||||
| 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, | |||||
| 0x02, 0x69, 0x64, 0x22, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, | |||||
| 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, | |||||
| 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x75, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, | |||||
| 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x50, 0x72, 0x6f, 0x64, | |||||
| 0x75, 0x63, 0x74, 0x12, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x50, 0x72, | |||||
| 0x6f, 0x64, 0x75, 0x63, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, | |||||
| 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x67, 0x65, 0x74, | |||||
| 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, | |||||
| 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x1a, 0x10, 0x2e, 0x70, 0x72, | |||||
| 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x56, 0x0a, | |||||
| 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, | |||||
| 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, | |||||
| 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, | |||||
| 0x24, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x73, 0x61, 0x67, 0x61, 0x2f, 0x65, | |||||
| 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, | |||||
| 0x6f, 0x64, 0x75, 0x63, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | |||||
| } | |||||
| var ( | |||||
| file_product_proto_rawDescOnce sync.Once | |||||
| file_product_proto_rawDescData = file_product_proto_rawDesc | |||||
| ) | |||||
| func file_product_proto_rawDescGZIP() []byte { | |||||
| file_product_proto_rawDescOnce.Do(func() { | |||||
| file_product_proto_rawDescData = protoimpl.X.CompressGZIP(file_product_proto_rawDescData) | |||||
| }) | |||||
| return file_product_proto_rawDescData | |||||
| } | |||||
| var file_product_proto_msgTypes = make([]protoimpl.MessageInfo, 2) | |||||
| var file_product_proto_goTypes = []interface{}{ | |||||
| (*Product)(nil), // 0: product.Product | |||||
| (*ProductId)(nil), // 1: product.ProductId | |||||
| } | |||||
| var file_product_proto_depIdxs = []int32{ | |||||
| 0, // 0: product.ProductInfo.addProduct:input_type -> product.Product | |||||
| 1, // 1: product.ProductInfo.getProduct:input_type -> product.ProductId | |||||
| 1, // 2: product.ProductInfo.addProduct:output_type -> product.ProductId | |||||
| 0, // 3: product.ProductInfo.getProduct:output_type -> product.Product | |||||
| 2, // [2:4] is the sub-list for method output_type | |||||
| 0, // [0:2] is the sub-list for method input_type | |||||
| 0, // [0:0] is the sub-list for extension type_name | |||||
| 0, // [0:0] is the sub-list for extension extendee | |||||
| 0, // [0:0] is the sub-list for field type_name | |||||
| } | |||||
| func init() { file_product_proto_init() } | |||||
| func file_product_proto_init() { | |||||
| if File_product_proto != nil { | |||||
| return | |||||
| } | |||||
| if !protoimpl.UnsafeEnabled { | |||||
| file_product_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | |||||
| switch v := v.(*Product); i { | |||||
| case 0: | |||||
| return &v.state | |||||
| case 1: | |||||
| return &v.sizeCache | |||||
| case 2: | |||||
| return &v.unknownFields | |||||
| default: | |||||
| return nil | |||||
| } | |||||
| } | |||||
| file_product_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | |||||
| switch v := v.(*ProductId); i { | |||||
| case 0: | |||||
| return &v.state | |||||
| case 1: | |||||
| return &v.sizeCache | |||||
| case 2: | |||||
| return &v.unknownFields | |||||
| default: | |||||
| return nil | |||||
| } | |||||
| } | |||||
| } | |||||
| type x struct{} | |||||
| out := protoimpl.TypeBuilder{ | |||||
| File: protoimpl.DescBuilder{ | |||||
| GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | |||||
| RawDescriptor: file_product_proto_rawDesc, | |||||
| NumEnums: 0, | |||||
| NumMessages: 2, | |||||
| NumExtensions: 0, | |||||
| NumServices: 1, | |||||
| }, | |||||
| GoTypes: file_product_proto_goTypes, | |||||
| DependencyIndexes: file_product_proto_depIdxs, | |||||
| MessageInfos: file_product_proto_msgTypes, | |||||
| }.Build() | |||||
| File_product_proto = out.File | |||||
| file_product_proto_rawDesc = nil | |||||
| file_product_proto_goTypes = nil | |||||
| file_product_proto_depIdxs = nil | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| syntax = "proto3"; | |||||
| package product; | |||||
| option go_package = "testdata/saga/engine/invoker/product"; | |||||
| option java_multiple_files = true; | |||||
| option java_package = "io.grpc.examples.helloworld"; | |||||
| option java_outer_classname = "HelloWorldProto"; | |||||
| service ProductInfo { | |||||
| rpc addProduct(Product) returns (ProductId); | |||||
| rpc getProduct(ProductId) returns (Product); | |||||
| } | |||||
| message Product { | |||||
| string id = 1; | |||||
| } | |||||
| message ProductId { | |||||
| string value = 1; | |||||
| } | |||||
| @@ -0,0 +1,150 @@ | |||||
| // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | |||||
| // versions: | |||||
| // - protoc-gen-go-grpc v1.3.0 | |||||
| // - protoc v4.25.3 | |||||
| // source: product.proto | |||||
| package product | |||||
| import ( | |||||
| context "context" | |||||
| grpc "google.golang.org/grpc" | |||||
| codes "google.golang.org/grpc/codes" | |||||
| status "google.golang.org/grpc/status" | |||||
| ) | |||||
| // This is a compile-time assertion to ensure that this generated file | |||||
| // is compatible with the grpc package it is being compiled against. | |||||
| // Requires gRPC-Go v1.32.0 or later. | |||||
| const _ = grpc.SupportPackageIsVersion7 | |||||
| const ( | |||||
| ProductInfo_AddProduct_FullMethodName = "/product.ProductInfo/addProduct" | |||||
| ProductInfo_GetProduct_FullMethodName = "/product.ProductInfo/getProduct" | |||||
| ) | |||||
| // ProductInfoClient is the client API for ProductInfo service. | |||||
| // | |||||
| // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | |||||
| type ProductInfoClient interface { | |||||
| // 添加商品 | |||||
| AddProduct(ctx context.Context, in *Product, opts ...grpc.CallOption) (*ProductId, error) | |||||
| // 获取商品 | |||||
| GetProduct(ctx context.Context, in *ProductId, opts ...grpc.CallOption) (*Product, error) | |||||
| } | |||||
| type productInfoClient struct { | |||||
| cc grpc.ClientConnInterface | |||||
| } | |||||
| func NewProductInfoClient(cc grpc.ClientConnInterface) ProductInfoClient { | |||||
| return &productInfoClient{cc} | |||||
| } | |||||
| func (c *productInfoClient) AddProduct(ctx context.Context, in *Product, opts ...grpc.CallOption) (*ProductId, error) { | |||||
| out := new(ProductId) | |||||
| err := c.cc.Invoke(ctx, ProductInfo_AddProduct_FullMethodName, in, out, opts...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return out, nil | |||||
| } | |||||
| func (c *productInfoClient) GetProduct(ctx context.Context, in *ProductId, opts ...grpc.CallOption) (*Product, error) { | |||||
| out := new(Product) | |||||
| err := c.cc.Invoke(ctx, ProductInfo_GetProduct_FullMethodName, in, out, opts...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return out, nil | |||||
| } | |||||
| // ProductInfoServer is the server API for ProductInfo service. | |||||
| // All implementations must embed UnimplementedProductInfoServer | |||||
| // for forward compatibility | |||||
| type ProductInfoServer interface { | |||||
| // 添加商品 | |||||
| AddProduct(context.Context, *Product) (*ProductId, error) | |||||
| // 获取商品 | |||||
| GetProduct(context.Context, *ProductId) (*Product, error) | |||||
| mustEmbedUnimplementedProductInfoServer() | |||||
| } | |||||
| // UnimplementedProductInfoServer must be embedded to have forward compatible implementations. | |||||
| type UnimplementedProductInfoServer struct { | |||||
| } | |||||
| func (UnimplementedProductInfoServer) AddProduct(context.Context, *Product) (*ProductId, error) { | |||||
| return nil, status.Errorf(codes.Unimplemented, "method AddProduct not implemented") | |||||
| } | |||||
| func (UnimplementedProductInfoServer) GetProduct(context.Context, *ProductId) (*Product, error) { | |||||
| return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") | |||||
| } | |||||
| func (UnimplementedProductInfoServer) mustEmbedUnimplementedProductInfoServer() {} | |||||
| // UnsafeProductInfoServer may be embedded to opt out of forward compatibility for this service. | |||||
| // Use of this interface is not recommended, as added methods to ProductInfoServer will | |||||
| // result in compilation errors. | |||||
| type UnsafeProductInfoServer interface { | |||||
| mustEmbedUnimplementedProductInfoServer() | |||||
| } | |||||
| func RegisterProductInfoServer(s grpc.ServiceRegistrar, srv ProductInfoServer) { | |||||
| s.RegisterService(&ProductInfo_ServiceDesc, srv) | |||||
| } | |||||
| func _ProductInfo_AddProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||||
| in := new(Product) | |||||
| if err := dec(in); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if interceptor == nil { | |||||
| return srv.(ProductInfoServer).AddProduct(ctx, in) | |||||
| } | |||||
| info := &grpc.UnaryServerInfo{ | |||||
| Server: srv, | |||||
| FullMethod: ProductInfo_AddProduct_FullMethodName, | |||||
| } | |||||
| handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||||
| return srv.(ProductInfoServer).AddProduct(ctx, req.(*Product)) | |||||
| } | |||||
| return interceptor(ctx, in, info, handler) | |||||
| } | |||||
| func _ProductInfo_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||||
| in := new(ProductId) | |||||
| if err := dec(in); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if interceptor == nil { | |||||
| return srv.(ProductInfoServer).GetProduct(ctx, in) | |||||
| } | |||||
| info := &grpc.UnaryServerInfo{ | |||||
| Server: srv, | |||||
| FullMethod: ProductInfo_GetProduct_FullMethodName, | |||||
| } | |||||
| handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||||
| return srv.(ProductInfoServer).GetProduct(ctx, req.(*ProductId)) | |||||
| } | |||||
| return interceptor(ctx, in, info, handler) | |||||
| } | |||||
| // ProductInfo_ServiceDesc is the grpc.ServiceDesc for ProductInfo service. | |||||
| // It's only intended for direct use with grpc.RegisterService, | |||||
| // and not to be introspected or modified (even as a copy) | |||||
| var ProductInfo_ServiceDesc = grpc.ServiceDesc{ | |||||
| ServiceName: "product.ProductInfo", | |||||
| HandlerType: (*ProductInfoServer)(nil), | |||||
| Methods: []grpc.MethodDesc{ | |||||
| { | |||||
| MethodName: "addProduct", | |||||
| Handler: _ProductInfo_AddProduct_Handler, | |||||
| }, | |||||
| { | |||||
| MethodName: "getProduct", | |||||
| Handler: _ProductInfo_GetProduct_Handler, | |||||
| }, | |||||
| }, | |||||
| Streams: []grpc.StreamDesc{}, | |||||
| Metadata: "product.proto", | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| { | |||||
| "Name": "simpleChoiceTestStateMachine", | |||||
| "Comment": "带条件分支的测试状态机定义", | |||||
| "StartState": "FirstState", | |||||
| "Version": "0.0.1", | |||||
| "States": { | |||||
| "FirstState": { | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "foo", | |||||
| "Next": "ChoiceState" | |||||
| }, | |||||
| "ChoiceState":{ | |||||
| "Type": "Choice", | |||||
| "Choices":[ | |||||
| { | |||||
| "Expression":"[a] == 1", | |||||
| "Next":"SecondState" | |||||
| }, | |||||
| { | |||||
| "Expression":"[a] == 2", | |||||
| "Next":"ThirdState" | |||||
| } | |||||
| ], | |||||
| "Default":"SecondState" | |||||
| }, | |||||
| "SecondState": { | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "bar" | |||||
| }, | |||||
| "ThirdState": { | |||||
| "Type": "ServiceTask", | |||||
| "ServiceName": "demoService", | |||||
| "ServiceMethod": "foo" | |||||
| } | |||||
| } | |||||
| } | |||||