├── commons ├── src │ ├── main │ │ ├── resources │ │ │ └── application.properties │ │ └── java │ │ │ └── io │ │ │ └── agilehandy │ │ │ └── commons │ │ │ ├── CommonsApplication.java │ │ │ └── api │ │ │ ├── database │ │ │ ├── DBRequest.java │ │ │ ├── DBTxnResponse.java │ │ │ ├── DBCancelRequest.java │ │ │ └── DBSubmitRequest.java │ │ │ ├── blockchain │ │ │ ├── BCRequest.java │ │ │ ├── BCTxnResponse.java │ │ │ ├── BCCancelRequest.java │ │ │ └── BCSubmitRequest.java │ │ │ ├── storage │ │ │ ├── FileRequest.java │ │ │ ├── FileTxnResponse.java │ │ │ ├── FileCancelRequest.java │ │ │ └── FileSubmitRequest.java │ │ │ └── jobs │ │ │ ├── JobState.java │ │ │ ├── JobExchange.java │ │ │ └── JobEvent.java │ └── test │ │ └── java │ │ └── io │ │ └── agilehandy │ │ └── commons │ │ └── CommonsApplicationTests.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── docs └── images │ └── Global_Txn_Manager.png ├── saga ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── src │ ├── main │ │ ├── resources │ │ │ ├── request.yml │ │ │ ├── schema-mysql.sql │ │ │ └── application.yml │ │ └── java │ │ │ └── io │ │ │ └── agilehandy │ │ │ └── txn │ │ │ ├── SagaApplication.java │ │ │ ├── saga │ │ │ ├── web │ │ │ │ ├── TxnResponse.java │ │ │ │ ├── TxnRequest.java │ │ │ │ └── WebController.java │ │ │ ├── job │ │ │ │ ├── JobKey.java │ │ │ │ ├── JobRepository.java │ │ │ │ └── Job.java │ │ │ ├── machine │ │ │ │ ├── SagaStateMachineBuilder.java │ │ │ │ ├── SagaMachinePersistJpaConfig.java │ │ │ │ ├── SagaStateMachineMonitor.java │ │ │ │ ├── SagaStateMachineJpaBuilder.java │ │ │ │ ├── SagaStateMachineInterceptor.java │ │ │ │ └── SagaStateMachine.java │ │ │ ├── SagaChannels.java │ │ │ └── Saga.java │ │ │ └── annotations │ │ │ └── StatesOnTransition.java │ └── test │ │ └── java │ │ └── io │ │ └── agilehandy │ │ └── txn │ │ └── SagaApplicationTests.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── remote-services ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── src │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── agilehandy │ │ │ └── remote │ │ │ └── RemoteServicesApplicationTests.java │ └── main │ │ ├── java │ │ └── io │ │ │ └── agilehandy │ │ │ └── remote │ │ │ ├── RemoteServicesApplication.java │ │ │ └── handlers │ │ │ ├── Utilities.java │ │ │ ├── EventChannels.java │ │ │ ├── BCHandler.java │ │ │ ├── DBHandler.java │ │ │ └── FileHandler.java │ │ └── resources │ │ └── application.yml ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── .gitignore ├── pom.xml ├── README.adoc ├── mvnw.cmd └── mvnw /commons/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haybu/demo-spring-distributed-transaction/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /docs/images/Global_Txn_Manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haybu/demo-spring-distributed-transaction/HEAD/docs/images/Global_Txn_Manager.png -------------------------------------------------------------------------------- /saga/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haybu/demo-spring-distributed-transaction/HEAD/saga/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /commons/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haybu/demo-spring-distributed-transaction/HEAD/commons/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /saga/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /commons/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /remote-services/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haybu/demo-spring-distributed-transaction/HEAD/remote-services/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /remote-services/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /saga/src/main/resources/request.yml: -------------------------------------------------------------------------------- 1 | { 2 | "id": 128, 3 | "storage": { 4 | "fileId": "file 1", 5 | "filename": "myfile.txt" 6 | }, 7 | "metadata": { 8 | "recordId": "record 1", 9 | "field1": "v1", 10 | "field2": "v2" 11 | }, 12 | "blockchain": { 13 | "contentId": "content 1" 14 | } 15 | } -------------------------------------------------------------------------------- /saga/src/test/java/io/agilehandy/txn/SagaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.txn; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class SagaApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/test/java/io/agilehandy/commons/CommonsApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.commons; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class CommonsApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /remote-services/src/test/java/io/agilehandy/remote/RemoteServicesApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.remote; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class RemoteServicesApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/CommonsApplication.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.commons; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CommonsApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CommonsApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/RemoteServicesApplication.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.remote; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RemoteServicesApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RemoteServicesApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /commons/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /saga/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /remote-services/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/SagaApplication.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.txn; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.domain.EntityScan; 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 7 | 8 | @SpringBootApplication 9 | @EnableJpaRepositories 10 | @EntityScan 11 | public class SagaApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SagaApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /saga/src/main/resources/schema-mysql.sql: -------------------------------------------------------------------------------- 1 | 2 | 3 | create table txn.jobs ( 4 | id INT NOT NULL, 5 | txn_id VARCHAR(100) NOT NULL, 6 | job_state VARCHAR(50) NOT NULL, 7 | start_ts DATE, 8 | end_ts DATE, 9 | updated_ts DATE, 10 | file_id VARCHAR (100), 11 | file_txn_status VARCHAR(100), 12 | file_txn_status_ts DATE, 13 | db_record_id VARCHAR(100), 14 | db_txn_status VARCHAR(100), 15 | db_txn_status_ts DATE, 16 | bc_record_id VARCHAR(100), 17 | bc_txn_status VARCHAR(100), 18 | bc_txn_status_ts DATE, 19 | PRIMARY KEY ( id, txn_id ) 20 | ); 21 | 22 | create table txn.state_machine ( 23 | machine_id VARCHAR (100), 24 | state VARCHAR(100), 25 | state_machine_context BLOB, 26 | PRIMARY KEY ( machine_id ) 27 | ); -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/database/DBRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.database; 17 | 18 | import java.util.UUID; 19 | 20 | public interface DBRequest { 21 | 22 | UUID getGlobalTxnId(); 23 | Long getJobId(); 24 | String getRecordId(); 25 | } 26 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/blockchain/BCRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.blockchain; 17 | 18 | import java.util.UUID; 19 | 20 | public interface BCRequest { 21 | 22 | UUID getGlobalTxnId(); 23 | Long getJobId(); 24 | String getContentId(); 25 | } 26 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/storage/FileRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.storage; 17 | 18 | import java.util.UUID; 19 | 20 | public interface FileRequest { 21 | 22 | UUID getGlobalTxnId(); 23 | Long getJobId(); 24 | String getFileId(); 25 | String getFilename(); 26 | } 27 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/jobs/JobState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.jobs; 17 | 18 | public enum JobState { 19 | 20 | JOB_START, 21 | JOB_COMPLETE, 22 | JOB_FAIL, 23 | JOB_TIME_OUT, 24 | 25 | FILE_SUBMIT, 26 | FILE_CANCEL, 27 | 28 | DB_SUBMIT, 29 | DB_CANCEL, 30 | 31 | BC_SUBMIT, 32 | BC_CANCEL 33 | ; 34 | } 35 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/jobs/JobExchange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.jobs; 17 | 18 | import java.util.UUID; 19 | 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | @Data 24 | @NoArgsConstructor 25 | public class JobExchange { 26 | 27 | private Long jobId; 28 | private UUID globalTxnId; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/web/TxnResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.web; 17 | 18 | import io.agilehandy.commons.api.jobs.JobState; 19 | import lombok.Data; 20 | import lombok.NoArgsConstructor; 21 | 22 | /** 23 | * @author Haytham Mohamed 24 | **/ 25 | 26 | @Data 27 | @NoArgsConstructor 28 | public class TxnResponse { 29 | 30 | private Long jobId; 31 | private JobState state; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/job/JobKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.job; 17 | 18 | import java.io.Serializable; 19 | 20 | /** 21 | * @author Haytham Mohamed 22 | **/ 23 | 24 | public class JobKey implements Serializable { 25 | Long jobId; 26 | String txnId; 27 | 28 | public JobKey() { 29 | this(null, null); 30 | } 31 | 32 | public JobKey(Long jobId, String txnId) { 33 | this.jobId = jobId; 34 | this.txnId = txnId; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /saga/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: saga 4 | cloud: 5 | stream: 6 | bindings: 7 | fileRequestOut: 8 | destination: storage 9 | producer.headerMode: headers 10 | contentType: application/json 11 | dbRequestOut: 12 | destination: database 13 | producer.headerMode: headers 14 | contentType: application/json 15 | bcRequestOut: 16 | destination: blockchain 17 | producer.headerMode: headers 18 | contentType: application/json 19 | txnResponseIn: 20 | destination: saga 21 | contentType: application/json 22 | consumer.headerMode: headers 23 | group: saga-group 24 | statemachine: 25 | data: 26 | jpa: 27 | repositories: 28 | enabled: true 29 | datasource: 30 | url: jdbc:mysql://localhost:3306/txn 31 | username: root 32 | password: root 33 | # initialization-mode: always 34 | jpa: 35 | show-sql: true 36 | hibernate: 37 | ddl-auto: none 38 | 39 | saga: 40 | timeout: 800 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaStateMachineBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | 21 | import org.springframework.statemachine.StateMachine; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | public interface SagaStateMachineBuilder { 27 | 28 | StateMachine getStateMachine(String txnId, boolean isFirstEvent); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/database/DBTxnResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.database; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobExchange; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class DBTxnResponse extends JobExchange { 30 | 31 | private JobEvent response; 32 | private String recordId; 33 | } 34 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/blockchain/BCTxnResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.blockchain; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobExchange; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class BCTxnResponse extends JobExchange { 30 | 31 | private JobEvent response; 32 | private String contentId; 33 | } 34 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/storage/FileTxnResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.storage; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobExchange; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class FileTxnResponse extends JobExchange { 30 | 31 | private JobEvent response; 32 | 33 | private String fileId; 34 | private String filename; 35 | } 36 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/database/DBCancelRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.database; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class DBCancelRequest extends JobExchange implements DBRequest { 30 | 31 | private JobState request = JobState.DB_CANCEL; 32 | private String recordId; 33 | } 34 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/blockchain/BCCancelRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.blockchain; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class BCCancelRequest extends JobExchange implements BCRequest { 30 | 31 | private JobState request = JobState.BC_CANCEL; 32 | private String contentId; 33 | } 34 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/blockchain/BCSubmitRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.blockchain; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class BCSubmitRequest extends JobExchange implements BCRequest { 30 | 31 | private JobState request = JobState.BC_SUBMIT; 32 | private String contentId; 33 | private byte[] content; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/jobs/JobEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.jobs; 17 | 18 | /** 19 | * @author Haytham Mohamed 20 | **/ 21 | public enum JobEvent { 22 | 23 | JOB_TXN_START, 24 | JOB_TXN_COMPLETE, 25 | JOB_TXN_FAIL, 26 | JOB_TXN_TIMEOUT, 27 | 28 | FILE_SUBMIT_COMPLETE, 29 | FILE_SUBMIT_FAIL, 30 | FILE_CANCEL_COMPLETE, 31 | FILE_CANCEL_FAIL, 32 | 33 | DB_SUBMIT_COMPLETE, 34 | DB_SUBMIT_FAIL, 35 | DB_CANCEL_COMPLETE, 36 | DB_CANCEL_FAIL, 37 | 38 | BC_SUBMIT_COMPLETE, 39 | BC_SUBMIT_FAIL, 40 | BC_CANCEL_COMPLETE, 41 | BC_CANCEL_FAIL 42 | ; 43 | } 44 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/storage/FileCancelRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.storage; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class FileCancelRequest extends JobExchange implements FileRequest { 30 | 31 | private JobState request = JobState.FILE_CANCEL; 32 | 33 | private String fileId; 34 | private String filename; 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/job/JobRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.job; 17 | 18 | import java.util.Collection; 19 | 20 | import org.springframework.data.jpa.repository.Query; 21 | import org.springframework.data.repository.CrudRepository; 22 | 23 | public interface JobRepository extends CrudRepository { 24 | 25 | @Query(value = "SELECT * from jobs where id = ?1 and job_state not in ('JOB_COMPLETE','JOB_FAIL','JOB_TIME_OUT')", 26 | nativeQuery = true) 27 | Collection findInProgressInstances(Long jobId); 28 | 29 | Collection findJobsByJobId(Long jobId); 30 | } 31 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/database/DBSubmitRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.database; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class DBSubmitRequest extends JobExchange implements DBRequest { 30 | 31 | private JobState request = JobState.DB_SUBMIT; 32 | private String recordId; 33 | private String field1; 34 | private String field2; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/web/TxnRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.web; 17 | 18 | import io.agilehandy.commons.api.blockchain.BCSubmitRequest; 19 | import io.agilehandy.commons.api.database.DBSubmitRequest; 20 | import io.agilehandy.commons.api.storage.FileSubmitRequest; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | /** 25 | * @author Haytham Mohamed 26 | **/ 27 | 28 | @Data 29 | @NoArgsConstructor 30 | public class TxnRequest { 31 | 32 | private Long id; 33 | 34 | FileSubmitRequest storage; 35 | 36 | DBSubmitRequest metadata; 37 | 38 | BCSubmitRequest blockchain; 39 | } 40 | -------------------------------------------------------------------------------- /commons/src/main/java/io/agilehandy/commons/api/storage/FileSubmitRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.commons.api.storage; 17 | 18 | import io.agilehandy.commons.api.jobs.JobExchange; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | /** 24 | * @author Haytham Mohamed 25 | **/ 26 | 27 | @Data 28 | @NoArgsConstructor 29 | public class FileSubmitRequest extends JobExchange implements FileRequest { 30 | 31 | private JobState request = JobState.FILE_SUBMIT; 32 | 33 | private String fileId; 34 | private String filename; 35 | private byte[] content; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/annotations/StatesOnTransition.java: -------------------------------------------------------------------------------- 1 | package io.agilehandy.txn.annotations;/* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | 22 | import io.agilehandy.commons.api.jobs.JobState; 23 | 24 | import org.springframework.statemachine.annotation.OnTransition; 25 | 26 | @Target(ElementType.METHOD) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @OnTransition 29 | public @interface StatesOnTransition { 30 | 31 | JobState[] source() default {}; 32 | 33 | JobState[] target() default {}; 34 | } 35 | -------------------------------------------------------------------------------- /commons/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.agilehandy 7 | demo-spring-distributed-transaction 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | commons 12 | commons 13 | commons 14 | jar 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter 20 | 21 | 22 | 23 | org.projectlombok 24 | lombok 25 | true 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | org.junit.vintage 34 | junit-vintage-engine 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/handlers/Utilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.remote.handlers; 17 | 18 | import java.util.Random; 19 | 20 | /** 21 | * @author Haytham Mohamed 22 | **/ 23 | public class Utilities { 24 | 25 | public static boolean simulateTxn(int max, int lowerBound, int higherBound 26 | , int maxSecondsToDelay) { 27 | int randomInteger = new Random().nextInt(max); 28 | delay(maxSecondsToDelay); 29 | 30 | if (randomInteger > lowerBound && randomInteger < higherBound) { 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | private static void delay(int maxSecondsToDelay) { 38 | try { 39 | int secondsToSleep = new Random().nextInt(maxSecondsToDelay); 40 | Thread.sleep(secondsToSleep * 1000); 41 | } catch (InterruptedException ie) { 42 | Thread.currentThread().interrupt(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/SagaChannels.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package io.agilehandy.txn.saga; 19 | 20 | import org.springframework.cloud.stream.annotation.Input; 21 | import org.springframework.cloud.stream.annotation.Output; 22 | import org.springframework.messaging.MessageChannel; 23 | import org.springframework.messaging.SubscribableChannel; 24 | 25 | /** 26 | * @author Haytham Mohamed 27 | **/ 28 | 29 | public interface SagaChannels { 30 | 31 | String FILE_REQUEST_OUT = "fileRequestOut"; 32 | String DB_REQUEST_OUT = "dbRequestOut"; 33 | String BC_REQUEST_OUT = "bcRequestOut"; 34 | String TXN_RESPONSE_IN = "txnResponseIn"; 35 | 36 | @Output(FILE_REQUEST_OUT) 37 | MessageChannel fileRequestOut(); 38 | 39 | @Output(DB_REQUEST_OUT) 40 | MessageChannel dbRequestOut(); 41 | 42 | @Output(BC_REQUEST_OUT) 43 | MessageChannel bcRequestOut(); 44 | 45 | @Input(TXN_RESPONSE_IN) 46 | SubscribableChannel txnResponseIn(); 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/handlers/EventChannels.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package io.agilehandy.remote.handlers; 19 | 20 | import org.springframework.cloud.stream.annotation.Input; 21 | import org.springframework.cloud.stream.annotation.Output; 22 | import org.springframework.messaging.MessageChannel; 23 | import org.springframework.messaging.SubscribableChannel; 24 | 25 | /** 26 | * @author Haytham Mohamed 27 | **/ 28 | 29 | public interface EventChannels { 30 | 31 | String FILE_REQUEST_IN = "fileRequestIn"; 32 | String DB_REQUEST_IN = "dbRequestIn"; 33 | String BC_REQUEST_IN = "bcRequestIn"; 34 | String TXN_RESPONSE_OUT = "txnResponseOut"; 35 | 36 | @Input(FILE_REQUEST_IN) 37 | SubscribableChannel fileRequestIn(); 38 | 39 | @Input(DB_REQUEST_IN) 40 | SubscribableChannel dbRequestIn(); 41 | 42 | @Input(BC_REQUEST_IN) 43 | SubscribableChannel bcRequestIn(); 44 | 45 | @Output(TXN_RESPONSE_OUT) 46 | MessageChannel txnResponseOut(); 47 | } 48 | -------------------------------------------------------------------------------- /remote-services/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | application: 6 | name: remote-services 7 | cloud: 8 | stream: 9 | bindings: 10 | fileRequestIn: 11 | destination: storage 12 | contentType: application/json 13 | consumer.headerMode: headers 14 | group: storage-group 15 | dbRequestIn: 16 | destination: database 17 | contentType: application/json 18 | consumer.headerMode: headers 19 | group: database-group 20 | bcRequestIn: 21 | destination: blockchain 22 | contentType: application/json 23 | consumer.headerMode: headers 24 | group: blockchain-group 25 | txnResponseOut: 26 | destination: saga 27 | producer.headerMode: headers 28 | contentType: application/json 29 | 30 | # random number between 0 to max 31 | # if random number falls between the lower and higher bound simulation assumes error in a txn 32 | # delay to simulate number of seconds a txn takes to process a request 33 | file: 34 | max: 10 35 | submit_lower_bound: 4 36 | submit_higher_bound: 7 37 | cancel_lower_bound: 4 38 | cancel_higher_bound: 7 39 | delay: 1 40 | timeout: 2 41 | 42 | db: 43 | max: 10 44 | submit_lower_bound: 3 45 | submit_higher_bound: 5 46 | cancel_lower_bound: 3 47 | cancel_higher_bound: 5 48 | delay: 1 49 | timeout: 2 50 | 51 | bc: 52 | max: 10 53 | submit_lower_bound: 4 54 | submit_higher_bound: 8 55 | cancel_lower_bound: 1 56 | cancel_higher_bound: 9 57 | delay: 2 58 | timeout: 3 59 | 60 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/web/WebController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.web; 17 | 18 | import io.agilehandy.commons.api.jobs.JobState; 19 | import io.agilehandy.txn.saga.Saga; 20 | 21 | import org.springframework.web.bind.annotation.PostMapping; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RestController; 24 | 25 | /** 26 | * @author Haytham Mohamed 27 | **/ 28 | 29 | @RestController 30 | public class WebController { 31 | 32 | private final Saga saga; 33 | 34 | public WebController(Saga saga) { 35 | this.saga = saga; 36 | } 37 | 38 | @PostMapping 39 | public TxnResponse runTransaction(@RequestBody TxnRequest request) { 40 | Long jobId = request.getId(); 41 | 42 | JobState state = saga.addTransactionRequest(request.getStorage()) 43 | .addTransactionRequest(request.getMetadata()) 44 | .addTransactionRequest(request.getBlockchain()) 45 | .orchestrate(jobId); 46 | 47 | TxnResponse response = new TxnResponse(); 48 | response.setJobId(jobId); 49 | response.setState(state); 50 | return response; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaMachinePersistJpaConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.statemachine.config.StateMachineFactory; 24 | import org.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor; 25 | import org.springframework.statemachine.data.jpa.JpaStateMachineRepository; 26 | import org.springframework.statemachine.persist.StateMachineRuntimePersister; 27 | import org.springframework.statemachine.service.DefaultStateMachineService; 28 | import org.springframework.statemachine.service.StateMachineService; 29 | 30 | /** 31 | * @author Haytham Mohamed 32 | **/ 33 | @Configuration 34 | public class SagaMachinePersistJpaConfig { 35 | 36 | @Bean 37 | public StateMachineRuntimePersister stateMachineRuntimePersister( 38 | JpaStateMachineRepository jpaStateMachineRepository) { 39 | return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); 40 | } 41 | 42 | @Bean 43 | public StateMachineService stateMachineService( 44 | StateMachineFactory stateMachineFactory, 45 | StateMachineRuntimePersister stateMachineRuntimePersister) { 46 | return new DefaultStateMachineService<>(stateMachineFactory, stateMachineRuntimePersister); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/job/Job.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.job; 17 | 18 | import java.util.Date; 19 | 20 | import javax.persistence.Column; 21 | import javax.persistence.Entity; 22 | import javax.persistence.Id; 23 | import javax.persistence.IdClass; 24 | import javax.persistence.Temporal; 25 | import javax.persistence.TemporalType; 26 | 27 | import lombok.Data; 28 | import lombok.NoArgsConstructor; 29 | import org.hibernate.annotations.CreationTimestamp; 30 | import org.hibernate.annotations.UpdateTimestamp; 31 | 32 | /** 33 | * @author Haytham Mohamed 34 | **/ 35 | 36 | @Entity (name = "jobs") 37 | @IdClass(JobKey.class) 38 | @Data 39 | @NoArgsConstructor 40 | public class Job { 41 | 42 | @Id 43 | @Column(name = "id") 44 | //@GeneratedValue(strategy= GenerationType.AUTO) 45 | private Long jobId; 46 | 47 | @Id 48 | private String txnId; 49 | 50 | private String jobState; 51 | 52 | @CreationTimestamp 53 | @Temporal(TemporalType.TIMESTAMP) 54 | private Date startTs; 55 | 56 | @Temporal(TemporalType.TIMESTAMP) 57 | private Date endTs; 58 | 59 | @UpdateTimestamp 60 | @Temporal(TemporalType.TIMESTAMP) 61 | private Date updatedTs; 62 | 63 | private String fileId; 64 | private String fileTxnStatus; 65 | @Temporal(TemporalType.TIMESTAMP) 66 | private Date fileTxnStatusTs; 67 | 68 | private String dbRecordId; 69 | private String dbTxnStatus; 70 | @Temporal(TemporalType.TIMESTAMP) 71 | private Date dbTxnStatusTs; 72 | 73 | private String bcRecordId; 74 | private String bcTxnStatus; 75 | @Temporal(TemporalType.TIMESTAMP) 76 | private Date bcTxnStatusTs; 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /remote-services/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.agilehandy 7 | demo-spring-distributed-transaction 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | remote-services 12 | 13 | remote-services 14 | remote-services 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-amqp 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-stream 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-stream-binder-rabbit 28 | 29 | 30 | 31 | io.agilehandy 32 | commons 33 | 0.0.1-SNAPSHOT 34 | 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | true 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | org.junit.vintage 48 | junit-vintage-engine 49 | 50 | 51 | 52 | 53 | org.springframework.amqp 54 | spring-rabbit-test 55 | test 56 | 57 | 58 | org.springframework.cloud 59 | spring-cloud-stream-test-support 60 | test 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaStateMachineMonitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import java.util.function.Function; 19 | 20 | import io.agilehandy.commons.api.jobs.JobEvent; 21 | import io.agilehandy.commons.api.jobs.JobState; 22 | import lombok.extern.log4j.Log4j2; 23 | import reactor.core.publisher.Mono; 24 | 25 | import org.springframework.statemachine.StateContext; 26 | import org.springframework.statemachine.StateMachine; 27 | import org.springframework.statemachine.monitor.AbstractStateMachineMonitor; 28 | import org.springframework.statemachine.transition.Transition; 29 | import org.springframework.stereotype.Component; 30 | 31 | /** 32 | * @author Haytham Mohamed 33 | **/ 34 | 35 | @Component 36 | @Log4j2 37 | public class SagaStateMachineMonitor extends AbstractStateMachineMonitor { 38 | 39 | @Override 40 | public void transition(StateMachine sm 41 | , Transition trans 42 | , long duration) { 43 | if (trans.getSource() == null || trans.getTarget() == null) { 44 | return; 45 | } 46 | String source = trans.getSource().getId().name(); 47 | String target = trans.getTarget().getId().name(); 48 | String txnId = sm.getUuid().toString(); 49 | 50 | log.info("[Monitor] Saga machine with txnId " + txnId 51 | + " takes " + duration + " milliseconds to move from " 52 | + source + " to " + target); 53 | 54 | } 55 | 56 | // leverage to redo if compensation transactions fail 57 | @Override 58 | public void action(StateMachine sm 59 | , Function, Mono> action 60 | , long duration) { 61 | 62 | log.info("Action takes " + duration + " milliseconds " 63 | + " on state " + sm.getState().getId().name()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaStateMachineJpaBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import io.agilehandy.txn.saga.job.JobRepository; 21 | import lombok.extern.log4j.Log4j2; 22 | 23 | import org.springframework.statemachine.StateMachine; 24 | import org.springframework.statemachine.service.StateMachineService; 25 | import org.springframework.stereotype.Component; 26 | 27 | /** 28 | * @author Haytham Mohamed 29 | **/ 30 | @Log4j2 31 | @Component 32 | public class SagaStateMachineJpaBuilder implements SagaStateMachineBuilder { 33 | 34 | private final StateMachineService stateMachineService; 35 | private final JobRepository jobRepository; 36 | 37 | public SagaStateMachineJpaBuilder(StateMachineService stateMachineService 38 | , JobRepository jobRepository) { 39 | this.stateMachineService = stateMachineService; 40 | this.jobRepository = jobRepository; 41 | } 42 | 43 | @Override 44 | public StateMachine getStateMachine(String txnId, boolean isFirstEvent) { 45 | log.info("Building a machine"); 46 | 47 | if(!isFirstEvent) { 48 | stateMachineService.releaseStateMachine(txnId); 49 | } 50 | 51 | StateMachine machine = stateMachineService.acquireStateMachine(txnId); 52 | 53 | // interceptor to sync job status 54 | machine.getStateMachineAccessor() 55 | .doWithAllRegions(sma -> 56 | sma.addStateMachineInterceptor(new SagaStateMachineInterceptor(stateMachineService 57 | , jobRepository))); 58 | 59 | machine.startReactively().block(); 60 | log.info("machine is now ready with state " + machine.getState().getId().name()); 61 | return machine; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.2.0.RELEASE 10 | 11 | 12 | 13 | io.agilehandy 14 | demo-spring-distributed-transaction 15 | 0.0.1-SNAPSHOT 16 | pom 17 | 18 | demo-spring-distributed-transaction 19 | Demo project for Spring Boot 20 | 21 | 22 | 1.8 23 | Hoxton.RC1 24 | 3.0.0.M1 25 | 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-dependencies 33 | ${spring-cloud.version} 34 | pom 35 | import 36 | 37 | 38 | org.springframework.statemachine 39 | spring-statemachine-bom 40 | ${spring-statemachine.version} 41 | pom 42 | import 43 | 44 | 45 | 46 | 47 | 48 | 49 | spring-snapshots 50 | Spring Snapshots 51 | https://repo.spring.io/snapshot 52 | 53 | true 54 | 55 | 56 | 57 | spring-milestones 58 | Spring Milestones 59 | https://repo.spring.io/milestone 60 | 61 | false 62 | 63 | 64 | 65 | 66 | 67 | 68 | spring-snapshots 69 | Spring Snapshots 70 | https://repo.spring.io/snapshot 71 | 72 | true 73 | 74 | 75 | 76 | spring-milestones 77 | Spring Milestones 78 | https://repo.spring.io/milestone 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | commons 87 | remote-services 88 | saga 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /saga/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.agilehandy 7 | demo-spring-distributed-transaction 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | saga 12 | 13 | saga 14 | saga 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | 23 | io.agilehandy 24 | commons 25 | 0.0.1-SNAPSHOT 26 | 27 | 28 | 29 | org.springframework.statemachine 30 | spring-statemachine-starter 31 | 32 | 33 | 34 | org.springframework.statemachine 35 | spring-statemachine-data-jpa 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-amqp 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-stream 45 | 46 | 47 | org.springframework.cloud 48 | spring-cloud-stream-binder-rabbit 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-data-jpa 54 | 55 | 56 | 61 | 62 | 63 | mysql 64 | mysql-connector-java 65 | runtime 66 | 67 | 68 | 69 | org.projectlombok 70 | lombok 71 | true 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-test 76 | test 77 | 78 | 79 | org.junit.vintage 80 | junit-vintage-engine 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-maven-plugin 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaStateMachineInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.jobs.JobState; 20 | import io.agilehandy.txn.saga.job.Job; 21 | import io.agilehandy.txn.saga.job.JobKey; 22 | import io.agilehandy.txn.saga.job.JobRepository; 23 | import lombok.extern.log4j.Log4j2; 24 | 25 | import org.springframework.messaging.Message; 26 | import org.springframework.statemachine.StateMachine; 27 | import org.springframework.statemachine.service.StateMachineService; 28 | import org.springframework.statemachine.state.State; 29 | import org.springframework.statemachine.support.StateMachineInterceptorAdapter; 30 | import org.springframework.statemachine.transition.Transition; 31 | 32 | /** 33 | * @author Haytham Mohamed 34 | **/ 35 | @Log4j2 36 | public class SagaStateMachineInterceptor extends StateMachineInterceptorAdapter { 37 | 38 | private final StateMachineService stateMachineService; 39 | private final JobRepository jobRepository; 40 | 41 | public SagaStateMachineInterceptor(StateMachineService stateMachineService 42 | , JobRepository jobRepository) { 43 | this.stateMachineService = stateMachineService; 44 | this.jobRepository = jobRepository; 45 | } 46 | 47 | @Override 48 | public void preStateChange(State state, Message message, Transition transition, StateMachine stateMachine) { 49 | //log.info("[interceptor] state machine built is at state: " + stateMachine.getState().getId().name()); 50 | Long tempJobId = Long.class.cast(message.getHeaders().getOrDefault("jobId", "")); 51 | String tempTxnId = String.class.cast(message.getHeaders().getOrDefault("txnId", "")); 52 | Job job = jobRepository.findById(new JobKey(tempJobId, tempTxnId)).orElse(null); 53 | if (job != null) { 54 | job.setJobState(state.getId().name()); 55 | jobRepository.save(job); 56 | log.info("[interceptor] state machine alters job with id " + tempJobId + " to state " 57 | + state.getId().name() + " in transaction " + tempTxnId); 58 | } 59 | } 60 | 61 | @Override 62 | public void postStateChange(State state, Message message, Transition transition, StateMachine stateMachine) { 63 | //if (stateMachine.isComplete()) { 64 | //stateMachineService.releaseStateMachine(stateMachine.getId()); 65 | //stateMachine.stopReactively().block(); 66 | //} 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/handlers/BCHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.remote.handlers; 17 | 18 | import io.agilehandy.commons.api.blockchain.BCCancelRequest; 19 | import io.agilehandy.commons.api.blockchain.BCRequest; 20 | import io.agilehandy.commons.api.blockchain.BCSubmitRequest; 21 | import io.agilehandy.commons.api.blockchain.BCTxnResponse; 22 | import io.agilehandy.commons.api.jobs.JobEvent; 23 | import lombok.extern.log4j.Log4j2; 24 | 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.cloud.stream.annotation.EnableBinding; 27 | import org.springframework.cloud.stream.annotation.StreamListener; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.messaging.Message; 30 | import org.springframework.messaging.handler.annotation.Payload; 31 | import org.springframework.messaging.support.MessageBuilder; 32 | 33 | /** 34 | * @author Haytham Mohamed 35 | **/ 36 | 37 | @Log4j2 38 | @Configuration 39 | @EnableBinding(EventChannels.class) 40 | public class BCHandler { 41 | 42 | @Value("${bc.max}") 43 | private int max; 44 | 45 | @Value("${bc.submit_higher_bound}") 46 | private int submitHigherBound; 47 | 48 | @Value("${bc.submit_lower_bound}") 49 | private int submitLowerBound; 50 | 51 | @Value("${bc.cancel_higher_bound}") 52 | private int cancelHigherBound; 53 | 54 | @Value("${bc.cancel_lower_bound}") 55 | private int cancelLowerBound; 56 | 57 | @Value("${bc.delay}") 58 | private int delay; 59 | 60 | private final EventChannels eventChannels; 61 | 62 | public BCHandler(EventChannels eventChannels) { 63 | this.eventChannels = eventChannels; 64 | } 65 | 66 | @StreamListener(target = EventChannels.BC_REQUEST_IN 67 | , condition = "headers['saga_request']=='BC_SUBMIT'") 68 | public void handleSubmitBC(@Payload BCSubmitRequest request) { 69 | log.info("remote blockchain service receives submit message to process"); 70 | boolean result = (request != null)? 71 | Utilities.simulateTxn(max, submitLowerBound, submitHigherBound, delay) : false; 72 | BCTxnResponse response = (result)? 73 | createBCResponse(request, JobEvent.BC_SUBMIT_COMPLETE) : 74 | createBCResponse(request, JobEvent.BC_SUBMIT_FAIL); 75 | sendResponse(response); 76 | } 77 | 78 | @StreamListener(target = EventChannels.BC_REQUEST_IN 79 | , condition = "headers['saga_request']=='BC_CANCEL'") 80 | public void handleCancelBC(@Payload BCCancelRequest request) { 81 | log.info("remote blockchain service receives cancel message to process"); 82 | boolean result = (request != null)? 83 | Utilities.simulateTxn(max, cancelLowerBound, cancelHigherBound, 1) : false; 84 | BCTxnResponse response = (result)? 85 | createBCResponse(request, JobEvent.BC_CANCEL_COMPLETE) : 86 | createBCResponse(request, JobEvent.BC_CANCEL_FAIL); 87 | sendResponse(response); 88 | } 89 | 90 | private void sendResponse(BCTxnResponse response) { 91 | Message message = MessageBuilder.withPayload(response) 92 | .setHeader("saga_response", response.getResponse()) 93 | .build(); 94 | eventChannels.txnResponseOut().send(message); 95 | } 96 | 97 | private BCTxnResponse createBCResponse(BCRequest request, JobEvent result) { 98 | BCTxnResponse response = new BCTxnResponse(); 99 | response.setResponse(result); 100 | response.setGlobalTxnId(request.getGlobalTxnId()); 101 | response.setJobId(request.getJobId()); 102 | return response; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/handlers/DBHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.remote.handlers; 17 | 18 | import io.agilehandy.commons.api.database.DBCancelRequest; 19 | import io.agilehandy.commons.api.database.DBRequest; 20 | import io.agilehandy.commons.api.database.DBSubmitRequest; 21 | import io.agilehandy.commons.api.database.DBTxnResponse; 22 | import io.agilehandy.commons.api.jobs.JobEvent; 23 | import lombok.extern.log4j.Log4j2; 24 | 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.cloud.stream.annotation.EnableBinding; 27 | import org.springframework.cloud.stream.annotation.StreamListener; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.messaging.Message; 30 | import org.springframework.messaging.handler.annotation.Payload; 31 | import org.springframework.messaging.support.MessageBuilder; 32 | 33 | /** 34 | * @author Haytham Mohamed 35 | **/ 36 | 37 | @Log4j2 38 | @Configuration 39 | @EnableBinding(EventChannels.class) 40 | public class DBHandler { 41 | 42 | @Value("${db.max}") 43 | private int max; 44 | 45 | @Value("${db.submit_higher_bound}") 46 | private int submitHigherBound; 47 | 48 | @Value("${db.submit_lower_bound}") 49 | private int submitLowerBound; 50 | 51 | @Value("${db.cancel_higher_bound}") 52 | private int cancelHigherBound; 53 | 54 | @Value("${db.cancel_lower_bound}") 55 | private int cancelLowerBound; 56 | 57 | @Value("${db.delay}") 58 | private int delay; 59 | 60 | private final EventChannels eventChannels; 61 | 62 | public DBHandler(EventChannels eventChannels) { 63 | this.eventChannels = eventChannels; 64 | } 65 | 66 | @StreamListener(target = EventChannels.DB_REQUEST_IN 67 | , condition = "headers['saga_request']=='DB_SUBMIT'") 68 | public void handleSubmitDB(@Payload DBSubmitRequest request) { 69 | log.info("remote database service receives submit message to process"); 70 | boolean result = (request != null)? 71 | Utilities.simulateTxn(max, submitLowerBound, submitHigherBound, delay) : false; 72 | DBTxnResponse response = (result)? 73 | createDBResponse(request, JobEvent.DB_SUBMIT_COMPLETE) : 74 | createDBResponse(request, JobEvent.DB_SUBMIT_FAIL); 75 | sendResponse(response); 76 | } 77 | 78 | @StreamListener(target = EventChannels.DB_REQUEST_IN 79 | , condition = "headers['saga_request']=='DB_CANCEL'") 80 | public void handleCancelDB(@Payload DBCancelRequest request) { 81 | log.info("remote database service receives cancel message to process"); 82 | boolean result = (request != null)? 83 | Utilities.simulateTxn(max, cancelLowerBound, cancelHigherBound, 1) : false; 84 | DBTxnResponse response = (result)? 85 | createDBResponse(request, JobEvent.DB_CANCEL_COMPLETE) : 86 | createDBResponse(request, JobEvent.DB_CANCEL_FAIL); 87 | sendResponse(response); 88 | } 89 | 90 | private void sendResponse(DBTxnResponse response) { 91 | Message message = MessageBuilder.withPayload(response) 92 | .setHeader("saga_response", response.getResponse()) 93 | .build(); 94 | eventChannels.txnResponseOut().send(message); 95 | } 96 | 97 | private DBTxnResponse createDBResponse(DBRequest request, JobEvent result) { 98 | DBTxnResponse response = new DBTxnResponse(); 99 | response.setResponse(result); 100 | response.setGlobalTxnId(request.getGlobalTxnId()); 101 | response.setJobId(request.getJobId()); 102 | response.setRecordId(request.getRecordId()); 103 | return response; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /remote-services/src/main/java/io/agilehandy/remote/handlers/FileHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.remote.handlers; 17 | 18 | import io.agilehandy.commons.api.jobs.JobEvent; 19 | import io.agilehandy.commons.api.storage.FileCancelRequest; 20 | import io.agilehandy.commons.api.storage.FileRequest; 21 | import io.agilehandy.commons.api.storage.FileSubmitRequest; 22 | import io.agilehandy.commons.api.storage.FileTxnResponse; 23 | import lombok.extern.log4j.Log4j2; 24 | 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.cloud.stream.annotation.EnableBinding; 27 | import org.springframework.cloud.stream.annotation.StreamListener; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.messaging.Message; 30 | import org.springframework.messaging.handler.annotation.Payload; 31 | import org.springframework.messaging.support.MessageBuilder; 32 | 33 | /** 34 | * @author Haytham Mohamed 35 | **/ 36 | 37 | @Log4j2 38 | @Configuration 39 | @EnableBinding(EventChannels.class) 40 | public class FileHandler { 41 | 42 | @Value("${file.max}") 43 | private int max; 44 | 45 | @Value("${file.submit_higher_bound}") 46 | private int submitHigherBound; 47 | 48 | @Value("${file.submit_lower_bound}") 49 | private int submitLowerBound; 50 | 51 | @Value("${file.cancel_higher_bound}") 52 | private int cancelHigherBound; 53 | 54 | @Value("${file.cancel_lower_bound}") 55 | private int cancelLowerBound; 56 | 57 | @Value("${file.delay}") 58 | private int delay; 59 | 60 | private final EventChannels eventChannels; 61 | 62 | public FileHandler(EventChannels eventChannels) { 63 | this.eventChannels = eventChannels; 64 | } 65 | 66 | @StreamListener(target = EventChannels.FILE_REQUEST_IN 67 | , condition = "headers['saga_request']=='FILE_SUBMIT'") 68 | public void handleSubmitFile(@Payload FileSubmitRequest request) { 69 | log.info("remote file service receives submit message to process"); 70 | boolean result = (request != null)? 71 | Utilities.simulateTxn(max, submitLowerBound, submitHigherBound, delay) : false; 72 | FileTxnResponse response = (result)? 73 | createFileResponse(request, JobEvent.FILE_SUBMIT_COMPLETE) : 74 | createFileResponse(request, JobEvent.FILE_SUBMIT_FAIL); 75 | sendResponse(response); 76 | } 77 | 78 | @StreamListener(target = EventChannels.FILE_REQUEST_IN 79 | , condition = "headers['saga_request']=='FILE_CANCEL'") 80 | public void handleCancelFile(@Payload FileCancelRequest request) { 81 | log.info("remote file service receives cancel message to process"); 82 | boolean result = (request != null)? 83 | Utilities.simulateTxn(max, cancelLowerBound, cancelHigherBound, 1) : false; 84 | FileTxnResponse response = (result)? 85 | createFileResponse(request, JobEvent.FILE_CANCEL_COMPLETE) : 86 | createFileResponse(request, JobEvent.FILE_CANCEL_FAIL); 87 | sendResponse(response); 88 | } 89 | 90 | private void sendResponse(FileTxnResponse response) { 91 | Message message = MessageBuilder.withPayload(response) 92 | .setHeader("saga_response", response.getResponse()) 93 | .build(); 94 | eventChannels.txnResponseOut().send(message); 95 | } 96 | 97 | private FileTxnResponse createFileResponse(FileRequest request, JobEvent result) { 98 | FileTxnResponse response = new FileTxnResponse(); 99 | response.setResponse(result); 100 | response.setGlobalTxnId(request.getGlobalTxnId()); 101 | response.setFileId(request.getFileId()); 102 | response.setFilename(request.getFilename()); 103 | response.setJobId(request.getJobId()); 104 | return response; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /saga/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } 72 | catch (IOException e) { 73 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 74 | } 75 | finally { 76 | try { 77 | if (mavenWrapperPropertyFileInputStream != null) { 78 | mavenWrapperPropertyFileInputStream.close(); 79 | } 80 | } 81 | catch (IOException e) { 82 | // Ignore ... 83 | } 84 | } 85 | } 86 | System.out.println("- Downloading from: : " + url); 87 | 88 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 89 | if (!outputFile.getParentFile().exists()) { 90 | if (!outputFile.getParentFile().mkdirs()) { 91 | System.out.println( 92 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 93 | } 94 | } 95 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 96 | try { 97 | downloadFileFromURL(url, outputFile); 98 | System.out.println("Done"); 99 | System.exit(0); 100 | } 101 | catch (Throwable e) { 102 | System.out.println("- Error downloading"); 103 | e.printStackTrace(); 104 | System.exit(1); 105 | } 106 | } 107 | 108 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /commons/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } 72 | catch (IOException e) { 73 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 74 | } 75 | finally { 76 | try { 77 | if (mavenWrapperPropertyFileInputStream != null) { 78 | mavenWrapperPropertyFileInputStream.close(); 79 | } 80 | } 81 | catch (IOException e) { 82 | // Ignore ... 83 | } 84 | } 85 | } 86 | System.out.println("- Downloading from: : " + url); 87 | 88 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 89 | if (!outputFile.getParentFile().exists()) { 90 | if (!outputFile.getParentFile().mkdirs()) { 91 | System.out.println( 92 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 93 | } 94 | } 95 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 96 | try { 97 | downloadFileFromURL(url, outputFile); 98 | System.out.println("Done"); 99 | System.exit(0); 100 | } 101 | catch (Throwable e) { 102 | System.out.println("- Error downloading"); 103 | e.printStackTrace(); 104 | System.exit(1); 105 | } 106 | } 107 | 108 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /remote-services/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } 72 | catch (IOException e) { 73 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 74 | } 75 | finally { 76 | try { 77 | if (mavenWrapperPropertyFileInputStream != null) { 78 | mavenWrapperPropertyFileInputStream.close(); 79 | } 80 | } 81 | catch (IOException e) { 82 | // Ignore ... 83 | } 84 | } 85 | } 86 | System.out.println("- Downloading from: : " + url); 87 | 88 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 89 | if (!outputFile.getParentFile().exists()) { 90 | if (!outputFile.getParentFile().mkdirs()) { 91 | System.out.println( 92 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 93 | } 94 | } 95 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 96 | try { 97 | downloadFileFromURL(url, outputFile); 98 | System.out.println("Done"); 99 | System.exit(0); 100 | } 101 | catch (Throwable e) { 102 | System.out.println("- Error downloading"); 103 | e.printStackTrace(); 104 | System.exit(1); 105 | } 106 | } 107 | 108 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Microservices Distributed Transaction Management 2 | 3 | This demo project illustrates managing a distributed transaction in a 4 | microservices architecture. 5 | 6 | A Global Transaction Manager (GTM) is implemented as a Saga pattern to 7 | manage orchestrating transactions across individual services. 8 | The implementation leverages a Spring State Machine to coordinate progression amongst 9 | global transaction collaborators. It also leverages Spring 10 | Cloud Stream to integrate with the collaborating services. 11 | 12 | image::./docs/images/Global_Txn_Manager.png[Distributed Transaction Management] 13 | 14 | ## Description 15 | 16 | A client can use the orchestrating Saga by injecting it as a bean and supply it with the many 17 | data requests target each remote transacting service. The implementation assumes a global logical 18 | identifier (as a job id) that in turn assumes the ownership of all individual items in the global 19 | transaction. So, all requests are submitted with one global identifier (job id). Every submission of 20 | a global transaction of same global identifier creates a new global transaction ID. I.e., a global 21 | transactions - identified with one job id - can have multiple executed instances each with its own 22 | identifying global transaction ID. 23 | 24 | The system is designed such that a global transaction will have only one global transaction instance 25 | executing at a time. Unless a global transaction (identified with a job id) reached its final state 26 | as completed or failed, no other invocation of the same global transaction can be executed. 27 | 28 | This illustration assumes three individual remote services. a file storage service, a database storage 29 | service and an immutable storage of a block chain. Only mocks of these services were implemented to mimic 30 | a local transaction of each with some processing delays. 31 | 32 | Once a request for each service is composed with the right data to store, 33 | a global transaction job identifier is then supplied with these requests 34 | to the saga to coordinate, as shown in the example below: 35 | 36 | ``` code[java] 37 | 38 | @Autowired 39 | private Saga saga; 40 | 41 | JobState state = saga.addTransactionRequest(fileStorageRequest) 42 | .addTransactionRequest(databaseStorageRequest) 43 | .addTransactionRequest(blockChainStorageRequest) 44 | .orchestrate(jobId); 45 | ``` 46 | 47 | This invocation would then return the result of final global transaction 48 | as a job state value. A final global transaction result could take one 49 | of these values: JOB_COMPLETE, JOB_FAIL or JOB_TIME_OUT. 50 | 51 | ## Transaction State Machine 52 | 53 | A global transaction would have different states throughout its lifecycle starting from 54 | a JOB_START state and end with either JOB_FAIL or JOB_TIME_OUT. In between, a transaction 55 | may be in a state where it tries to submit a request to store a file or cancel such request 56 | (FILE_SUBMIT / FILE_CANCEL), and same states for the database and block chain requests 57 | (DB_SUBMIT / DB_CANCEL, BC_SUBMIT / BC_CANCEL). Transition between these states and how 58 | the global transaction should do when it gets to each of these states are managed with 59 | a Spring State Machine. Using a Spring State Machine give us a luxury of: 60 | 61 | * avoiding cluttering code with a lot of if/else statements of the orchestration cases 62 | * defining a state machine as a well-defined configured setup in a java class 63 | * defining what the transitions are permitted to do 64 | * defining what action should be executed when making a transition or when a state change 65 | * abstracting away how a transaction progresses or performs action from the orchestrator, and delegates 66 | to the state machine to figure that out 67 | 68 | The state machine receives events from the orchestrating Saga. Each event reflects what the Saga 69 | has in turns receives from each individual service to indicate whether a local transaction is 70 | successful or not. 71 | 72 | The state machine reacts by transitioning to a proper state and as a 73 | result fires an appropriate action of what should happen next. It does so by publishing 74 | a request via one of the appropriate channel message. Each individual service subscribes 75 | to its request channels, listens to coming streams of requests, performs the requested action 76 | and publishes a transaction result to the one response channel that the Saga orchestrator is listening to. 77 | 78 | 79 | ## Transaction Orchestrator 80 | 81 | The transaction is orchestrated with a Saga which acts as a stream listener that subscribes to 82 | a message channel. Each message is published from one of the remote services to contain the transaction 83 | result. The Saga receives this message and reacts by firing a proper event to the state machine. 84 | 85 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /saga/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /commons/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /remote-services/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /saga/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /commons/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /remote-services/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/machine/SagaStateMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga.machine; 17 | 18 | import io.agilehandy.commons.api.blockchain.BCCancelRequest; 19 | import io.agilehandy.commons.api.blockchain.BCSubmitRequest; 20 | import io.agilehandy.commons.api.database.DBCancelRequest; 21 | import io.agilehandy.commons.api.database.DBSubmitRequest; 22 | import io.agilehandy.commons.api.jobs.JobEvent; 23 | import io.agilehandy.commons.api.jobs.JobState; 24 | import io.agilehandy.commons.api.storage.FileCancelRequest; 25 | import io.agilehandy.commons.api.storage.FileSubmitRequest; 26 | import io.agilehandy.txn.saga.SagaChannels; 27 | import lombok.extern.log4j.Log4j2; 28 | 29 | import org.springframework.messaging.Message; 30 | import org.springframework.messaging.support.MessageBuilder; 31 | import org.springframework.statemachine.StateContext; 32 | import org.springframework.statemachine.StateMachine; 33 | import org.springframework.statemachine.config.EnableStateMachineFactory; 34 | import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; 35 | import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; 36 | import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; 37 | import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; 38 | import org.springframework.statemachine.listener.StateMachineListener; 39 | import org.springframework.statemachine.listener.StateMachineListenerAdapter; 40 | import org.springframework.statemachine.persist.StateMachineRuntimePersister; 41 | import org.springframework.statemachine.state.State; 42 | import org.springframework.stereotype.Component; 43 | 44 | /** 45 | * @author Haytham Mohamed 46 | **/ 47 | 48 | @EnableStateMachineFactory(contextEvents = false) 49 | @Component 50 | @Log4j2 51 | public class SagaStateMachine extends EnumStateMachineConfigurerAdapter { 52 | 53 | private final SagaChannels channels; 54 | private final SagaStateMachineMonitor monitor; 55 | private final StateMachineRuntimePersister stateMachineRuntimePersister; 56 | 57 | public SagaStateMachine(SagaChannels channels, SagaStateMachineMonitor monitor, StateMachineRuntimePersister stateMachineRuntimePersister, StateMachineRuntimePersister stateMachineRuntimePersister1) { 58 | this.channels = channels; 59 | this.monitor = monitor; 60 | this.stateMachineRuntimePersister = stateMachineRuntimePersister1; 61 | } 62 | 63 | @Override 64 | public void configure(StateMachineConfigurationConfigurer config) throws Exception { 65 | StateMachineListener listener = new StateMachineListenerAdapter() { 66 | @Override 67 | public void stateMachineStarted(StateMachine stateMachine) { 68 | log.info("[listener] State machine started"); 69 | } 70 | 71 | @Override 72 | public void stateExited(State state) { 73 | log.info("[listener] State machine exited"); 74 | } 75 | 76 | @Override 77 | public void stateMachineStopped(StateMachine stateMachine) { 78 | log.info("[listener] State machine stopped"); 79 | } 80 | 81 | }; 82 | 83 | config 84 | .withConfiguration() 85 | .autoStartup(false) 86 | //.machineId("saga-machine") 87 | .listener(listener) 88 | .and().withMonitoring().monitor(monitor) 89 | .and().withPersistence().runtimePersister(stateMachineRuntimePersister); 90 | ; 91 | } 92 | 93 | @Override 94 | public void configure(StateMachineStateConfigurer states) throws Exception { 95 | states.withStates() 96 | .initial(JobState.JOB_START) 97 | .end(JobState.JOB_COMPLETE) 98 | .end(JobState.JOB_FAIL) 99 | .end(JobState.JOB_TIME_OUT) 100 | .state(JobState.FILE_SUBMIT, context -> handleFileSubmitAction(context), null) 101 | .state(JobState.FILE_CANCEL, context -> handleFileCancelAction(context), null) 102 | .state(JobState.DB_SUBMIT, context -> handleDbSubmitAction(context), null) 103 | .state(JobState.DB_CANCEL, context -> handleDbCancelAction(context), null) 104 | .state(JobState.BC_SUBMIT, context -> handleBcSubmitAction(context), null) 105 | .state(JobState.BC_CANCEL, context -> handleBcCancelAction(context), null) 106 | ; 107 | ; 108 | } 109 | 110 | // look a typical demonstrating picture of a state machine at 111 | // https://dzone.com/articles/distributed-sagas-for-microservices 112 | @Override 113 | public void configure(StateMachineTransitionConfigurer transitions) 114 | throws Exception { 115 | transitions 116 | .withExternal() 117 | .source(JobState.JOB_START) 118 | .target(JobState.FILE_SUBMIT) 119 | .event(JobEvent.JOB_TXN_START) 120 | //.action(context -> handleFileSubmitAction(context)) 121 | .and().withExternal() 122 | .source(JobState.FILE_SUBMIT) 123 | .target(JobState.DB_SUBMIT) 124 | .event(JobEvent.FILE_SUBMIT_COMPLETE) 125 | //.action(context -> handleDbSubmitAction(context)) 126 | .and().withExternal() 127 | .source(JobState.DB_SUBMIT) 128 | .target(JobState.BC_SUBMIT) 129 | .event(JobEvent.DB_SUBMIT_COMPLETE) 130 | //.action(context -> handleBcSubmitAction(context)) 131 | .and().withExternal() 132 | .source(JobState.BC_SUBMIT) 133 | .target(JobState.JOB_COMPLETE) 134 | .event(JobEvent.BC_SUBMIT_COMPLETE) // end of happy path 135 | 136 | .and().withExternal() // if file submit fails 137 | .source(JobState.FILE_SUBMIT) 138 | .target(JobState.FILE_CANCEL) 139 | .event(JobEvent.FILE_SUBMIT_FAIL) 140 | //.action(context -> handleFileCancelAction(context) ) 141 | .and().withExternal() 142 | .source(JobState.FILE_CANCEL) 143 | .target(JobState.JOB_FAIL) 144 | .event(JobEvent.FILE_CANCEL_COMPLETE) 145 | 146 | .and().withExternal() // if DB submit fails 147 | .source(JobState.DB_SUBMIT) 148 | .target(JobState.DB_CANCEL) 149 | .event(JobEvent.DB_SUBMIT_FAIL) 150 | //.action(context -> handleDbCancelAction(context)) 151 | .and().withExternal() 152 | .source(JobState.DB_CANCEL) 153 | .target(JobState.FILE_CANCEL) 154 | .event(JobEvent.DB_CANCEL_COMPLETE) 155 | //.action(context -> handleFileCancelAction(context) ) 156 | 157 | .and().withExternal() // if BC submit fails 158 | .source(JobState.BC_SUBMIT) 159 | .target(JobState.BC_CANCEL) 160 | .event(JobEvent.BC_SUBMIT_FAIL) 161 | //.action(context -> handleBcCancelAction(context)) 162 | .and().withExternal() 163 | .source(JobState.BC_CANCEL) 164 | .target(JobState.DB_CANCEL) 165 | .event(JobEvent.BC_CANCEL_COMPLETE) 166 | .and().withExternal() 167 | .source(JobState.DB_CANCEL) 168 | .target(JobState.FILE_CANCEL) 169 | .event(JobEvent.DB_CANCEL_COMPLETE) 170 | 171 | // repeating actions 172 | .and().withInternal() 173 | .source(JobState.FILE_CANCEL) 174 | .action(context -> handleFileCancelAction(context)) 175 | .event(JobEvent.FILE_CANCEL_FAIL) 176 | 177 | .and().withInternal() 178 | .source(JobState.DB_CANCEL) 179 | .action(context -> handleDbCancelAction(context)) 180 | .event(JobEvent.DB_CANCEL_FAIL) 181 | 182 | .and().withInternal() 183 | .source(JobState.BC_CANCEL) 184 | .action(context -> handleBcCancelAction(context)) 185 | .event(JobEvent.BC_CANCEL_FAIL) 186 | 187 | // timeouts 188 | .and().withExternal() 189 | .source(JobState.FILE_SUBMIT) 190 | .target(JobState.JOB_TIME_OUT) 191 | .event(JobEvent.JOB_TXN_TIMEOUT) 192 | 193 | .and().withExternal() 194 | .source(JobState.FILE_CANCEL) 195 | .target(JobState.JOB_TIME_OUT) 196 | .event(JobEvent.JOB_TXN_TIMEOUT) 197 | 198 | .and().withExternal() 199 | .source(JobState.DB_SUBMIT) 200 | .target(JobState.JOB_TIME_OUT) 201 | .event(JobEvent.JOB_TXN_TIMEOUT) 202 | 203 | .and().withExternal() 204 | .source(JobState.DB_CANCEL) 205 | .target(JobState.JOB_TIME_OUT) 206 | .event(JobEvent.JOB_TXN_TIMEOUT) 207 | 208 | .and().withExternal() 209 | .source(JobState.BC_SUBMIT) 210 | .target(JobState.JOB_TIME_OUT) 211 | .event(JobEvent.JOB_TXN_TIMEOUT) 212 | 213 | .and().withExternal() 214 | .source(JobState.BC_CANCEL) 215 | .target(JobState.JOB_TIME_OUT) 216 | .event(JobEvent.JOB_TXN_TIMEOUT) 217 | ; 218 | } 219 | 220 | 221 | /* 222 | Machine Actions 223 | */ 224 | 225 | // submit a file txn 226 | //@StatesOnTransition(source = JobState.JOB_START, target = JobState.FILE_SUBMIT) 227 | public void handleFileSubmitAction(StateContext stateContext) { 228 | log.info("state machine transits from JOB_START to FILE_SUBMIT"); 229 | FileSubmitRequest request = 230 | (FileSubmitRequest) stateContext.getExtendedState().getVariables().get("request"); 231 | Message message = MessageBuilder.withPayload(request) 232 | .setHeader("saga_request", "FILE_SUBMIT") 233 | .build(); 234 | channels.fileRequestOut().send(message); 235 | } 236 | 237 | // cancel a file txn 238 | //@StatesOnTransition (source = {JobState.FILE_SUBMIT, JobState.DB_CANCEL}, target = JobState.FILE_CANCEL) 239 | public void handleFileCancelAction(StateContext stateContext) { 240 | log.info("state machine transits from FILE_SUBMIT or DB_CANCEL to FILE_CANCEL"); 241 | FileCancelRequest request = 242 | (FileCancelRequest) stateContext.getExtendedState().getVariables().get("request"); 243 | Message message = MessageBuilder.withPayload(request) 244 | .setHeader("saga_request", "FILE_CANCEL") 245 | .build(); 246 | channels.fileRequestOut().send(message); 247 | } 248 | 249 | // submit a db record: send a message request 250 | //@StatesOnTransition (source = JobState.FILE_SUBMIT, target = JobState.DB_SUBMIT) 251 | public void handleDbSubmitAction(StateContext stateContext) { 252 | log.info("state machine transits from FILE_SUBMIT to DB_SUBMIT"); 253 | DBSubmitRequest request = 254 | (DBSubmitRequest) stateContext.getExtendedState().getVariables().get("request"); 255 | Message message = MessageBuilder.withPayload(request) 256 | .setHeader("saga_request", "DB_SUBMIT") 257 | .build(); 258 | channels.dbRequestOut().send(message); 259 | } 260 | 261 | // cancel a db txn 262 | //@StatesOnTransition (source = {JobState.DB_SUBMIT, JobState.BC_CANCEL}, target = JobState.DB_CANCEL) 263 | public void handleDbCancelAction(StateContext stateContext) { 264 | log.info("state machine transits from DB_SUBMIT or BC_CANCEL to DB_CANCEL"); 265 | DBCancelRequest request = 266 | (DBCancelRequest) stateContext.getExtendedState().getVariables().get("request"); 267 | Message message = MessageBuilder.withPayload(request) 268 | .setHeader("saga_request", "DB_CANCEL") 269 | .build(); 270 | channels.dbRequestOut().send(message); 271 | } 272 | 273 | // submit a bc record: send a message request 274 | //@StatesOnTransition (source = JobState.DB_SUBMIT, target = JobState.BC_SUBMIT) 275 | public void handleBcSubmitAction(StateContext stateContext) { 276 | log.info("state machine transits from DB_SUBMIT to BC_SUBMIT"); 277 | BCSubmitRequest request = 278 | (BCSubmitRequest) stateContext.getExtendedState().getVariables().get("request"); 279 | Message message = MessageBuilder.withPayload(request) 280 | .setHeader("saga_request", "BC_SUBMIT") 281 | .build(); 282 | channels.bcRequestOut().send(message); 283 | } 284 | 285 | // cancel a bc txn 286 | //@StatesOnTransition (source = JobState.BC_SUBMIT, target = JobState.BC_CANCEL) 287 | public void handleBcCancelAction(StateContext stateContext) { 288 | log.info("state machine transits from BC_SUBMIT to BC_CANCEL"); 289 | BCCancelRequest request = 290 | (BCCancelRequest) stateContext.getExtendedState().getVariables().get("request"); 291 | Message message = MessageBuilder.withPayload(request) 292 | .setHeader("saga_request", "BC_CANCEL") 293 | .build(); 294 | channels.bcRequestOut().send(message); 295 | } 296 | 297 | } 298 | -------------------------------------------------------------------------------- /saga/src/main/java/io/agilehandy/txn/saga/Saga.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.agilehandy.txn.saga; 17 | 18 | import java.sql.Timestamp; 19 | import java.util.Collection; 20 | import java.util.UUID; 21 | 22 | import io.agilehandy.commons.api.blockchain.BCCancelRequest; 23 | import io.agilehandy.commons.api.blockchain.BCSubmitRequest; 24 | import io.agilehandy.commons.api.blockchain.BCTxnResponse; 25 | import io.agilehandy.commons.api.database.DBCancelRequest; 26 | import io.agilehandy.commons.api.database.DBSubmitRequest; 27 | import io.agilehandy.commons.api.database.DBTxnResponse; 28 | import io.agilehandy.commons.api.jobs.JobEvent; 29 | import io.agilehandy.commons.api.jobs.JobExchange; 30 | import io.agilehandy.commons.api.jobs.JobState; 31 | import io.agilehandy.commons.api.storage.FileCancelRequest; 32 | import io.agilehandy.commons.api.storage.FileSubmitRequest; 33 | import io.agilehandy.commons.api.storage.FileTxnResponse; 34 | import io.agilehandy.txn.saga.job.Job; 35 | import io.agilehandy.txn.saga.job.JobRepository; 36 | import io.agilehandy.txn.saga.machine.SagaStateMachineBuilder; 37 | import lombok.Data; 38 | import lombok.extern.log4j.Log4j2; 39 | import reactor.core.publisher.Mono; 40 | 41 | import org.springframework.beans.factory.annotation.Value; 42 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 43 | import org.springframework.cloud.stream.annotation.EnableBinding; 44 | import org.springframework.cloud.stream.annotation.StreamListener; 45 | import org.springframework.context.annotation.Scope; 46 | import org.springframework.messaging.Message; 47 | import org.springframework.messaging.handler.annotation.Payload; 48 | import org.springframework.messaging.support.MessageBuilder; 49 | import org.springframework.statemachine.StateMachine; 50 | 51 | /** 52 | * @author Haytham Mohamed 53 | **/ 54 | 55 | @Data 56 | @Log4j2 57 | @EnableBinding(SagaChannels.class) 58 | @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 59 | public class Saga { 60 | 61 | private final JobRepository jobRepository; 62 | private final SagaStateMachineBuilder builder; 63 | 64 | private FileSubmitRequest fileSubmitRequest; 65 | private DBSubmitRequest dbSubmitRequest; 66 | private BCSubmitRequest bcSubmitRequest; 67 | 68 | StateMachine currentStateMachine; 69 | 70 | @Value("${saga.timeout}") 71 | private long timeout; 72 | 73 | private Saga() { 74 | this (null, null); 75 | } 76 | 77 | public Saga(JobRepository repository, SagaStateMachineBuilder builder) { 78 | log.info("A new saga is instantiated."); 79 | this.jobRepository = repository; 80 | this.builder = builder; 81 | } 82 | 83 | public JobState orchestrate(Long jobId, FileSubmitRequest fsr 84 | , DBSubmitRequest dbr, BCSubmitRequest bcr) { 85 | fileSubmitRequest = fsr; 86 | dbSubmitRequest = dbr; 87 | bcSubmitRequest = bcr; 88 | return orchestrate(jobId); 89 | } 90 | 91 | public JobState orchestrate(Long jobId) { 92 | UUID txnId = UUID.randomUUID(); 93 | if (!canOrchestrate(jobId)) { 94 | log.info("Cannot run this transaction as same Job with ID " + jobId + " is in progress."); 95 | return JobState.JOB_FAIL; 96 | } 97 | 98 | log.info("saga orchestration starts."); 99 | if (fileSubmitRequest != null) { 100 | fileSubmitRequest.setGlobalTxnId(txnId); 101 | fileSubmitRequest.setJobId(jobId); 102 | } 103 | if (dbSubmitRequest != null) { 104 | dbSubmitRequest.setGlobalTxnId(txnId); 105 | dbSubmitRequest.setJobId(jobId); 106 | } 107 | if (bcSubmitRequest != null) { 108 | bcSubmitRequest.setGlobalTxnId(txnId); 109 | bcSubmitRequest.setJobId(jobId); 110 | } 111 | Job job = createJob(jobId, txnId.toString(), fileSubmitRequest, dbSubmitRequest, bcSubmitRequest); 112 | return start(job.getJobId(), txnId.toString()); 113 | } 114 | 115 | // checks job transaction DB to make sure there is no inflight txn 116 | private boolean canOrchestrate(Long jobId) { 117 | Collection jobs = jobRepository.findInProgressInstances(jobId); 118 | return jobs.isEmpty()? true : false; 119 | } 120 | 121 | // start by submitting the file 122 | private JobState start(Long jobId, String txnId) { 123 | signalStateMachine(jobId, txnId, fileSubmitRequest, JobEvent.JOB_TXN_START); 124 | long startTimestamp = System.currentTimeMillis(); 125 | long currentTimestamp = startTimestamp; 126 | boolean isTimeOut = false; 127 | 128 | while(!isTerminalState()) { 129 | if (currentTimestamp - startTimestamp > timeout) { 130 | log.info("Distributed transaction for job Id " + jobId + " and txn Id " + txnId + " is timed out"); 131 | isTimeOut = true; 132 | } 133 | currentTimestamp = System.currentTimeMillis(); 134 | } 135 | 136 | if (isTimeOut) { 137 | signalStateMachine(jobId, txnId, null, JobEvent.JOB_TXN_TIMEOUT); 138 | } 139 | 140 | JobState finalState = currentStateMachine.getState().getId(); 141 | log.info("Final Transaction state is " + finalState + ". Job completes in " 142 | + (currentTimestamp - startTimestamp) + " milliseconds"); 143 | return finalState; 144 | } 145 | 146 | private boolean isTerminalState() { 147 | JobState state = JobState.valueOf(currentStateMachine.getState().getId().name()); 148 | return (state == JobState.JOB_TIME_OUT || state == JobState.JOB_COMPLETE 149 | || state == JobState.JOB_FAIL); 150 | } 151 | 152 | private Job createJob(Long jobId, String txnId, FileSubmitRequest fs, DBSubmitRequest db, BCSubmitRequest bc) { 153 | Job job = new Job(); 154 | job.setJobId(jobId); 155 | job.setTxnId(txnId); 156 | job.setJobState(JobState.JOB_START.name()); 157 | if (fs != null) { 158 | job.setFileId(fs.getFileId().toString()); 159 | } 160 | if (db != null) { 161 | job.setDbRecordId(db.getRecordId().toString()); 162 | } 163 | if (bc != null) { 164 | job.setBcRecordId(bc.getContentId().toString()); 165 | } 166 | job.setStartTs(new Timestamp(System.currentTimeMillis())); 167 | return jobRepository.save(job); 168 | } 169 | 170 | public void signalStateMachine(Long jobId, String txnId 171 | , JobExchange request, JobEvent signal) { 172 | boolean isFirstCall = (signal == JobEvent.JOB_TXN_START); 173 | currentStateMachine = builder.getStateMachine(txnId, isFirstCall); 174 | 175 | if (currentStateMachine.isComplete()) { 176 | log.info("Transaction already existed. No action to take."); 177 | return; 178 | } 179 | 180 | log.info("machine signal to send is " + signal 181 | + " to machine with state " + currentStateMachine.getState().getId().name()); 182 | if (request != null) { 183 | currentStateMachine.getExtendedState().getVariables().put("request", request); 184 | } 185 | Message message = MessageBuilder.withPayload(signal) 186 | .setHeader("jobId", jobId) 187 | .setHeader("txnId", txnId) 188 | .build(); 189 | currentStateMachine.sendEvent(Mono.just(message)).blockLast(); 190 | } 191 | 192 | public Saga addTransactionRequest(FileSubmitRequest request) { 193 | this.fileSubmitRequest = request; 194 | return this; 195 | } 196 | 197 | public Saga addTransactionRequest(DBSubmitRequest request) { 198 | this.dbSubmitRequest = request; 199 | return this; 200 | } 201 | 202 | public Saga addTransactionRequest(BCSubmitRequest request) { 203 | this.bcSubmitRequest = request; 204 | return this; 205 | } 206 | 207 | /** 208 | * ---------------------------------------- 209 | * stream listeners 210 | * ---------------------------------------- 211 | */ 212 | 213 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 214 | , condition = "headers['saga_response']=='FILE_SUBMIT_COMPLETE'") 215 | public void handleFileSubmitComplete(@Payload FileTxnResponse response) { 216 | log.info("Saga receives response from remote file service with signal FILE_SUBMIT_COMPLETE"); 217 | signalStateMachine(response.getJobId() 218 | , response.getGlobalTxnId().toString() 219 | ,dbSubmitRequest, JobEvent.FILE_SUBMIT_COMPLETE); 220 | } 221 | 222 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 223 | , condition = "headers['saga_response']=='FILE_SUBMIT_FAIL'") 224 | public void handleFileSubmitFail(@Payload FileTxnResponse response) { 225 | log.info("Saga receives response from remote file service with signal FILE_SUBMIT_FAIL"); 226 | FileCancelRequest fileCancelRequest = new FileCancelRequest(); 227 | fileCancelRequest.setJobId(response.getJobId()); 228 | fileCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 229 | fileCancelRequest.setFilename(response.getFilename()); 230 | fileCancelRequest.setFileId(response.getFileId()); 231 | signalStateMachine(response.getJobId() 232 | , response.getGlobalTxnId().toString() 233 | ,fileCancelRequest, JobEvent.FILE_SUBMIT_FAIL); 234 | 235 | } 236 | 237 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 238 | , condition = "headers['saga_response']=='FILE_CANCEL_COMPLETE'") 239 | public void handleFileCancelComplete(@Payload FileTxnResponse response) { 240 | log.info("Saga receives response from remote file service with signal FILE_CANCEL_COMPLETE"); 241 | signalStateMachine(response.getJobId() 242 | , response.getGlobalTxnId().toString() 243 | ,null, JobEvent.FILE_CANCEL_COMPLETE); 244 | } 245 | 246 | // TODO: needs a continuous timer action in the state machine transition 247 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 248 | , condition = "headers['saga_response']=='FILE_CANCEL_FAIL'") 249 | public void handleFileCancelFail(@Payload FileTxnResponse response) { 250 | log.info("Saga receives response from remote file service with signal FILE_CANCEL_FAIL"); 251 | FileCancelRequest fileCancelRequest = new FileCancelRequest(); 252 | fileCancelRequest.setJobId(response.getJobId()); 253 | fileCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 254 | fileCancelRequest.setFilename(response.getFilename()); 255 | fileCancelRequest.setFileId(response.getFileId()); 256 | signalStateMachine(response.getJobId() 257 | , response.getGlobalTxnId().toString() 258 | ,fileCancelRequest, JobEvent.FILE_CANCEL_FAIL); 259 | } 260 | 261 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 262 | , condition = "headers['saga_response']=='DB_SUBMIT_COMPLETE'") 263 | public void handleDBSubmitComplete(@Payload DBTxnResponse response) { 264 | log.info("Saga receives response from remote database service with signal DB_SUBMIT_COMPLETE"); 265 | signalStateMachine(response.getJobId() 266 | , response.getGlobalTxnId().toString() 267 | ,bcSubmitRequest, JobEvent.DB_SUBMIT_COMPLETE); 268 | 269 | } 270 | 271 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 272 | , condition = "headers['saga_response']=='DB_SUBMIT_FAIL'") 273 | public void handleDBSubmitFail(@Payload DBTxnResponse response) { 274 | log.info("Saga receives response from remote database service with signal DB_SUBMIT_FAIL"); 275 | DBCancelRequest dbCancelRequest = new DBCancelRequest(); 276 | dbCancelRequest.setJobId(response.getJobId()); 277 | dbCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 278 | dbCancelRequest.setRecordId(response.getRecordId()); 279 | signalStateMachine(response.getJobId() 280 | , response.getGlobalTxnId().toString() 281 | ,dbCancelRequest, JobEvent.DB_SUBMIT_FAIL); 282 | 283 | } 284 | 285 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 286 | , condition = "headers['saga_response']=='DB_CANCEL_COMPLETE'") 287 | public void handleDBCancelComplete(@Payload DBTxnResponse response) { 288 | log.info("Saga receives response from remote database service with signal DB_CANCEL_COMPLETE"); 289 | FileCancelRequest fileCancelRequest = new FileCancelRequest(); 290 | fileCancelRequest.setJobId(response.getJobId()); 291 | fileCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 292 | fileCancelRequest.setFilename(fileSubmitRequest.getFilename()); 293 | fileCancelRequest.setFileId(fileSubmitRequest.getFileId()); 294 | signalStateMachine(response.getJobId() 295 | , response.getGlobalTxnId().toString() 296 | ,fileCancelRequest, JobEvent.DB_CANCEL_COMPLETE); 297 | 298 | } 299 | 300 | // TODO: needs a continuous timer action in the state machine transition 301 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 302 | , condition = "headers['saga_response']=='DB_CANCEL_FAIL'") 303 | public void handleDBCancelFail(@Payload DBTxnResponse response) { 304 | log.info("Saga receives response from remote database service with signal DB_CANCEL_FAIL"); 305 | DBCancelRequest dbCancelRequest = new DBCancelRequest(); 306 | dbCancelRequest.setJobId(response.getJobId()); 307 | dbCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 308 | dbCancelRequest.setRecordId(response.getRecordId()); 309 | signalStateMachine(response.getJobId() 310 | , response.getGlobalTxnId().toString() 311 | ,dbCancelRequest, JobEvent.DB_CANCEL_FAIL); 312 | 313 | } 314 | 315 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 316 | , condition = "headers['saga_response']=='BC_SUBMIT_COMPLETE'") 317 | public void handleBCSubmitComplete(@Payload BCTxnResponse response) { 318 | log.info("Saga receives response from remote blockchain service with signal BC_SUBMIT_COMPLETE"); 319 | signalStateMachine(response.getJobId() 320 | , response.getGlobalTxnId().toString() 321 | ,null, JobEvent.BC_SUBMIT_COMPLETE); 322 | 323 | } 324 | 325 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 326 | , condition = "headers['saga_response']=='BC_SUBMIT_FAIL'") 327 | public void handleBCSubmitFail(@Payload BCTxnResponse response) { 328 | log.info("Saga receives response from remote blockchain service with signal BC_SUBMIT_FAIL"); 329 | BCCancelRequest bcCancelRequest = new BCCancelRequest(); 330 | bcCancelRequest.setJobId(response.getJobId()); 331 | bcCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 332 | bcCancelRequest.setContentId(response.getContentId()); 333 | signalStateMachine(response.getJobId() 334 | , response.getGlobalTxnId().toString() 335 | ,bcCancelRequest, JobEvent.BC_SUBMIT_FAIL); 336 | } 337 | 338 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 339 | , condition = "headers['saga_response']=='BC_CANCEL_COMPLETE'") 340 | public void handleBCCancelComplete(@Payload BCTxnResponse response) { 341 | log.info("Saga receives response from remote blockchain service with signal BC_CANCEL_COMPLETE"); 342 | DBCancelRequest dbCancelRequest = new DBCancelRequest(); 343 | dbCancelRequest.setJobId(response.getJobId()); 344 | dbCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 345 | dbCancelRequest.setRecordId(dbSubmitRequest.getRecordId()); 346 | signalStateMachine(response.getJobId() 347 | , response.getGlobalTxnId().toString() 348 | ,dbCancelRequest, JobEvent.BC_CANCEL_COMPLETE); 349 | } 350 | 351 | // TODO: needs a continuous timer action in the state machine transition 352 | @StreamListener(target = SagaChannels.TXN_RESPONSE_IN 353 | , condition = "headers['saga_response']=='BC_CANCEL_FAIL'") 354 | public void handleBCCancelFail(@Payload BCTxnResponse response) { 355 | log.info("Saga receives response from remote blockchain service with signal BC_CANCEL_FAIL"); 356 | BCCancelRequest bcCancelRequest = new BCCancelRequest(); 357 | bcCancelRequest.setJobId(response.getJobId()); 358 | bcCancelRequest.setGlobalTxnId(response.getGlobalTxnId()); 359 | bcCancelRequest.setContentId(response.getContentId()); 360 | signalStateMachine(response.getJobId() 361 | , response.getGlobalTxnId().toString() 362 | ,bcCancelRequest, JobEvent.BC_CANCEL_FAIL); 363 | } 364 | 365 | } 366 | --------------------------------------------------------------------------------