├── codecov.yml ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── resources ├── readme-images │ ├── step1.png │ ├── step2.png │ ├── step3.png │ └── architecture.png └── docker files │ ├── test containers │ └── docker-compose.yml │ ├── Run project locally │ └── docker-compose.yml │ └── Run project on docker │ └── docker-compose.yml ├── order ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── sylleryum │ │ │ │ └── order │ │ │ │ ├── exception │ │ │ │ └── OrderException.java │ │ │ │ ├── entity │ │ │ │ ├── OrderPojoWrapper.java │ │ │ │ ├── ItemDAO.java │ │ │ │ └── OrderDAO.java │ │ │ │ ├── repository │ │ │ │ └── OrderRepository.java │ │ │ │ ├── service │ │ │ │ ├── OrderService.java │ │ │ │ └── OrderManagementService.java │ │ │ │ ├── util │ │ │ │ ├── OrderConverter.java │ │ │ │ └── OrderGenerator.java │ │ │ │ ├── producer │ │ │ │ └── KafkaProducer.java │ │ │ │ ├── controller │ │ │ │ └── OrderController.java │ │ │ │ ├── config │ │ │ │ └── KafkaConfig.java │ │ │ │ └── OrderApplication.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── sylleryum │ │ └── order │ │ ├── OrderApplicationTests.java │ │ ├── testUtil │ │ └── ListenerPayloadChecker.java │ │ ├── controller │ │ └── OrderControllerTest.java │ │ ├── util │ │ ├── OrderConverterTest.java │ │ └── OrderGeneratorTest.java │ │ ├── service │ │ ├── OrderServiceTest.java │ │ └── OrderManagementServiceTest.java │ │ └── producer │ │ └── KafkaProducerTest.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── stock ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── sylleryum │ │ │ │ └── stock │ │ │ │ ├── exceptions │ │ │ │ └── StockException.java │ │ │ │ ├── entity │ │ │ │ ├── ItemsWapper.java │ │ │ │ └── StockItem.java │ │ │ │ ├── repository │ │ │ │ └── StockRepository.java │ │ │ │ ├── controller │ │ │ │ └── StockController.java │ │ │ │ ├── producer │ │ │ │ └── KafkaProducer.java │ │ │ │ ├── util │ │ │ │ └── DbDataInitializer.java │ │ │ │ ├── StockApplication.java │ │ │ │ └── service │ │ │ │ ├── StockService.java │ │ │ │ └── OrderManagementService.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── sylleryum │ │ └── stock │ │ ├── util │ │ └── DbDataInitializerTest.java │ │ └── service │ │ ├── StockServiceTest.java │ │ └── OrderStockManagementServiceTest.java ├── .gitignore ├── pom.xml └── mvnw.cmd ├── common ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── sylleryum │ │ │ └── common │ │ │ ├── util │ │ │ └── OrderStatus.java │ │ │ ├── entity │ │ │ ├── Item.java │ │ │ └── Order.java │ │ │ └── config │ │ │ └── GlobalConfigs.java │ │ └── resources │ │ └── shared.properties ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── payment ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ ├── main │ │ ├── resources │ │ │ └── application.properties │ │ └── java │ │ │ └── com │ │ │ └── sylleryum │ │ │ └── payment │ │ │ ├── producer │ │ │ └── KafkaProducer.java │ │ │ ├── PaymentApplication.java │ │ │ └── service │ │ │ ├── PaymentService.java │ │ │ └── OrderPaymentManagementService.java │ └── test │ │ └── java │ │ └── com │ │ └── sylleryum │ │ └── payment │ │ ├── PaymentApplicationTests.java │ │ └── service │ │ ├── PaymentServiceTest.java │ │ └── OrderManagementServiceTest.java ├── .gitignore ├── pom.xml └── mvnw.cmd ├── notification ├── src │ ├── main │ │ ├── resources │ │ │ └── application.properties │ │ └── java │ │ │ └── com │ │ │ └── sylleryum │ │ │ └── notifications │ │ │ ├── NotificationApplication.java │ │ │ └── service │ │ │ └── NotificationService.java │ └── test │ │ └── java │ │ └── com │ │ └── sylleryum │ │ └── notifications │ │ └── service │ │ └── NotificationServiceTest.java └── pom.xml ├── HELP.md ├── .gitignore ├── .github └── workflows │ └── workflow.yml ├── pom.xml ├── docker-compose.yml ├── README.md ├── mvnw.cmd └── mvnw /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | #exclude POJOs 3 | - "**/entity" 4 | #excludes (PSVM) main method 5 | - "**Application.java" 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /resources/readme-images/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/resources/readme-images/step1.png -------------------------------------------------------------------------------- /resources/readme-images/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/resources/readme-images/step2.png -------------------------------------------------------------------------------- /resources/readme-images/step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/resources/readme-images/step3.png -------------------------------------------------------------------------------- /order/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/order/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /stock/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/stock/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /common/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/common/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /payment/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/payment/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /resources/readme-images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sylleryum/kafka-microservices-with-saga/HEAD/resources/readme-images/architecture.png -------------------------------------------------------------------------------- /notification/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.config.import=classpath:shared.properties 2 | 3 | spring.kafka.client-id=notifications 4 | spring.kafka.consumer.group-id=notifications 5 | 6 | server.port=8084 7 | logging.level.com.sylleryum=INFO -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/exception/OrderException.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.exception; 2 | 3 | public class OrderException extends RuntimeException{ 4 | 5 | public OrderException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/exceptions/StockException.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.exceptions; 2 | 3 | public class StockException extends RuntimeException{ 4 | 5 | public StockException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /payment/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.config.import=classpath:shared.properties 2 | 3 | spring.application.name=payment 4 | spring.kafka.client-id=payment 5 | spring.kafka.consumer.group-id=payment 6 | 7 | server.port=8083 8 | logging.level.com.sylleryum=INFO -------------------------------------------------------------------------------- /common/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /order/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /payment/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /stock/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/entity/ItemsWapper.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class ItemsWapper { 13 | 14 | private List items; 15 | } 16 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/OrderApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | 8 | @SpringBootTest 9 | class OrderApplicationTests { 10 | 11 | @Test 12 | @Ignore 13 | void generalTests(){ 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/entity/OrderPojoWrapper.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.entity; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Data 13 | public class OrderPojoWrapper { 14 | private List orders; 15 | } 16 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | 5 | For further reference, please consider the following sections: 6 | 7 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 8 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.4/maven-plugin/reference/html/) 9 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.4/maven-plugin/reference/html/#build-image) 10 | 11 | -------------------------------------------------------------------------------- /order/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.config.import=classpath:shared.properties 2 | 3 | #DB 4 | spring.datasource.url= jdbc:postgresql://localhost:5432/kafka 5 | spring.datasource.username=postgres 6 | spring.datasource.password=admin123 7 | spring.jpa.hibernate.ddl-auto=create-drop 8 | 9 | #Producer 10 | spring.kafka.producer.client-id=order-producer 11 | 12 | #Stream 13 | spring.kafka.streams.client-id=order 14 | spring.kafka.streams.application-id=order 15 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/repository/StockRepository.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.repository; 2 | 3 | import com.sylleryum.stock.entity.StockItem; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | 10 | public interface StockRepository extends MongoRepository { 11 | 12 | 13 | ListfindByItemNumberIn(List itemNumbers); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/sylleryum/common/util/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.common.util; 2 | 3 | public class OrderStatus { 4 | public final static String NEW = "New"; 5 | public final static String ORDER_CONFIRMED = "Order confirmed"; 6 | public final static String SUCCESS = "Success"; 7 | public final static String FAILED = "Failed"; 8 | 9 | public final static String ROLLBACK = "Rollback"; 10 | public final static String CANCELLED = "Cancelled"; 11 | } 12 | -------------------------------------------------------------------------------- /payment/src/test/java/com/sylleryum/payment/PaymentApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.annotation.Repeat; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 9 | 10 | @SpringBootTest 11 | class PaymentApplicationTests { 12 | 13 | @Test 14 | @Ignore 15 | void generalTests() { 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 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 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /order/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 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 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /stock/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 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 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /payment/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 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 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/entity/StockItem.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.entity; 2 | 3 | import lombok.*; 4 | import org.springframework.data.mongodb.core.index.Indexed; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Builder 11 | @Document(collection = "stock") 12 | public class StockItem { 13 | 14 | private String id; 15 | private String itemNumber; 16 | @Indexed(unique = true) 17 | private String description; 18 | private long quantity; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.repository; 2 | 3 | import com.sylleryum.order.entity.OrderDAO; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | import java.util.Optional; 9 | 10 | @Repository 11 | @Transactional 12 | public interface OrderRepository extends CrudRepository { 13 | 14 | @Transactional 15 | Optional findByOrderNumber(String orderNumber); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /stock/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.config.import=classpath:shared.properties 2 | 3 | #DB 4 | spring.application.name=stock 5 | spring.data.mongodb.uri=mongodb://root:Mongo2022!@localhost:27017/kafka?authSource=admin 6 | spring.data.mongodb.authentication-database=admin 7 | spring.data.mongodb.auto-index-creation=true 8 | mongodb.collection.name=stock 9 | 10 | #how many documents to be inserted in the DB when this service starts 11 | initdb.amount=1000 12 | 13 | #Kafka 14 | spring.kafka.client-id=stock 15 | spring.kafka.consumer.group-id=stock 16 | 17 | server.port=8082 18 | logging.level.com.sylleryum=INFO -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/entity/ItemDAO.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonBackReference; 4 | import lombok.*; 5 | 6 | import javax.persistence.*; 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | @Entity 14 | public class ItemDAO { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.AUTO) 18 | private Long id; 19 | private String itemNumber; 20 | @ManyToOne 21 | @JoinColumn(name = "order_id") 22 | @JsonBackReference 23 | private OrderDAO order; 24 | private long quantity; 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Set up JDK 11 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: 11.0.x 15 | - name: Build the stack 16 | working-directory: ./resources/docker files/test containers/ 17 | run: docker-compose up -d 18 | - name: Build with Maven 19 | run: mvn test org.jacoco:jacoco-maven-plugin:report -e -X 20 | - name: Upload coverage to Codecov 21 | uses: codecov/codecov-action@v2 22 | with: 23 | flags: unittests 24 | 25 | name: codecov-umbrella 26 | path_to_write_report: ./coverage/codecov_report.txt 27 | verbose: true -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/controller/StockController.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.controller; 2 | 3 | import com.sylleryum.stock.entity.ItemsWapper; 4 | import com.sylleryum.stock.service.StockService; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("api/v1") 12 | public class StockController { 13 | 14 | private final StockService itemService; 15 | 16 | public StockController(StockService itemService) { 17 | this.itemService = itemService; 18 | } 19 | 20 | @GetMapping("/stock") 21 | public ResponseEntity getItems(){ 22 | 23 | ItemsWapper items = new ItemsWapper(itemService.findAll()); 24 | return ResponseEntity.ok(items); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/sylleryum/common/entity/Item.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.common.entity; 2 | 3 | public class Item { 4 | 5 | private String itemNumber; 6 | private long quantity; 7 | 8 | public Item() { 9 | } 10 | 11 | public Item(String itemNumber, long quantity) { 12 | this.itemNumber = itemNumber; 13 | this.quantity = quantity; 14 | } 15 | 16 | public String getItemNumber() { 17 | return itemNumber; 18 | } 19 | 20 | public void setItemNumber(String itemNumber) { 21 | this.itemNumber = itemNumber; 22 | } 23 | 24 | 25 | @Override 26 | public String toString() { 27 | return "Item{" + 28 | "itemNumber='" + itemNumber + '\'' + 29 | ", quantity=" + quantity + 30 | '}'; 31 | } 32 | 33 | public long getQuantity() { 34 | return quantity; 35 | } 36 | 37 | public void setQuantity(long quantity) { 38 | this.quantity = quantity; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.service; 2 | 3 | import com.sylleryum.order.entity.OrderDAO; 4 | import com.sylleryum.order.repository.OrderRepository; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.Optional; 9 | 10 | @Service 11 | @Slf4j 12 | public class OrderService { 13 | 14 | private final OrderRepository orderRepository; 15 | 16 | public OrderService(OrderRepository orderRepository) { 17 | this.orderRepository = orderRepository; 18 | } 19 | 20 | public OrderDAO save(OrderDAO orderDAO){ 21 | return orderRepository.save(orderDAO); 22 | } 23 | 24 | public Iterable saveAll(Iterable entities) { 25 | return orderRepository.saveAll(entities); 26 | } 27 | 28 | public Optional findByOrderNumber(String orderNumber) { 29 | return orderRepository.findByOrderNumber(orderNumber); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stock/src/test/java/com/sylleryum/stock/util/DbDataInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.util; 2 | 3 | import com.sylleryum.stock.entity.StockItem; 4 | import com.sylleryum.stock.service.StockService; 5 | import org.junit.Ignore; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import java.util.Comparator; 12 | 13 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 14 | 15 | 16 | @SpringBootTest 17 | class DbDataInitializerTest { 18 | 19 | @Autowired 20 | DbDataInitializer dbDataInitializer; 21 | @Autowired 22 | StockService stockService; 23 | @Value("${initdb.amount}") 24 | long documentAmount; 25 | 26 | @Test 27 | void initDbData() { 28 | dbDataInitializer.initDbData(); 29 | 30 | assertThat(stockService.count()).isEqualTo(documentAmount); 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/util/OrderConverter.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.util; 2 | 3 | import com.sylleryum.common.entity.Item; 4 | import com.sylleryum.common.entity.Order; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | @Component 12 | public class OrderConverter { 13 | 14 | public Order orderDaoToKafka(OrderDAO orderDAO) { 15 | Order orderKafka = new Order(orderDAO.getOrderNumber(), 16 | orderDAO.getItems().stream().map(itemDAO -> 17 | new Item(itemDAO.getItemNumber(), itemDAO.getQuantity())).collect(Collectors.toList()), 18 | orderDAO.getOrderPrice(), orderDAO.getStockStatus(), orderDAO.getStockStatusReason(), orderDAO.getPaymentStatus(), orderDAO.getPaymentStatusReason()); 19 | return orderKafka; 20 | } 21 | 22 | public List listOrderDaoToKafka(List orderDAOList){ 23 | return orderDAOList.stream().map(this::orderDaoToKafka).collect(Collectors.toList()); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/producer/KafkaProducer.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.producer; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.kafka.core.KafkaTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Slf4j 13 | public class KafkaProducer { 14 | 15 | 16 | private final KafkaTemplate kafkaTemplate; 17 | private final String kafkaTopicStock; 18 | 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(KafkaProducer.class); 21 | 22 | public KafkaProducer(KafkaTemplate kafkaTemplate, 23 | @Value("${topic.name.stock}") String kafkaTopicStock) { 24 | this.kafkaTemplate = kafkaTemplate; 25 | this.kafkaTopicStock = kafkaTopicStock; 26 | } 27 | 28 | public void send(String key, Order payload) { 29 | log.debug("sending payload={}, key={}, topic={}", payload, key, kafkaTopicStock); 30 | kafkaTemplate.send(kafkaTopicStock, key, payload); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /notification/src/main/java/com/sylleryum/notifications/NotificationApplication.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.notifications; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.notifications.service.NotificationService; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.kafka.annotation.EnableKafka; 9 | import org.springframework.kafka.annotation.KafkaListener; 10 | 11 | @SpringBootApplication 12 | @ComponentScan(basePackages = "com.sylleryum") 13 | @EnableKafka 14 | public class NotificationApplication{ 15 | 16 | private final NotificationService notificationService; 17 | 18 | public NotificationApplication(NotificationService notificationService) { 19 | this.notificationService = notificationService; 20 | } 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(NotificationApplication.class, args); 24 | } 25 | 26 | @KafkaListener(topics = "${topic.name.notification}") 27 | private void listener(Order order) { 28 | notificationService.sendNotification(order); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /payment/src/main/java/com/sylleryum/payment/producer/KafkaProducer.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment.producer; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.kafka.core.KafkaTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Slf4j 13 | public class KafkaProducer { 14 | 15 | 16 | private final KafkaTemplate kafkaTemplate; 17 | private final String kafkaTopicPayment; 18 | 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(KafkaProducer.class); 21 | 22 | public KafkaProducer(KafkaTemplate kafkaTemplate, 23 | @Value("${topic.name.payment}") String kafkaTopicPayment) { 24 | this.kafkaTemplate = kafkaTemplate; 25 | this.kafkaTopicPayment = kafkaTopicPayment; 26 | } 27 | 28 | public void send(String key, Order payload) { 29 | log.debug("sending payload={}, key={}, topic={}", payload, key, kafkaTopicPayment); 30 | kafkaTemplate.send(kafkaTopicPayment, key, payload); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/com/sylleryum/common/config/GlobalConfigs.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.common.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.PropertySource; 6 | 7 | @Configuration 8 | @PropertySource("classpath:shared.properties") 9 | public class GlobalConfigs { 10 | 11 | private final int statusStock; 12 | private final int statusPayment; 13 | private final String itemPrefix; 14 | public final int SUCCESS = 1; 15 | public final int FAILURE = -1; 16 | 17 | public GlobalConfigs(@Value("${order.status.stock}") int statusStock, 18 | @Value("${order.status.payment}") int statusPayment, 19 | @Value("${item.number.prefix}") String itemPrefix) { 20 | this.statusStock = statusStock; 21 | this.statusPayment = statusPayment; 22 | this.itemPrefix = itemPrefix; 23 | ; 24 | } 25 | 26 | public int statusStock() { 27 | return statusStock; 28 | } 29 | 30 | public int statusPayment() { 31 | return statusPayment; 32 | } 33 | 34 | public String itemPrefix() { 35 | return itemPrefix; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /payment/src/main/java/com/sylleryum/payment/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.payment.service.OrderPaymentManagementService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.kafka.annotation.EnableKafka; 10 | import org.springframework.kafka.annotation.KafkaListener; 11 | 12 | @SpringBootApplication 13 | @ComponentScan(basePackages = "com.sylleryum") 14 | @EnableKafka 15 | @Slf4j 16 | public class PaymentApplication { 17 | 18 | private final OrderPaymentManagementService orderManagementService; 19 | 20 | public PaymentApplication(OrderPaymentManagementService orderManagementService) { 21 | this.orderManagementService = orderManagementService; 22 | } 23 | 24 | public static void main(String[] args) { 25 | SpringApplication.run(PaymentApplication.class, args); 26 | } 27 | 28 | @KafkaListener(topics = "${topic.name.order}") 29 | private void listener(Order order) { 30 | orderManagementService.processPayment(order); 31 | } 32 | } -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/testUtil/ListenerPayloadChecker.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.testUtil; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import org.apache.kafka.clients.consumer.ConsumerRecord; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.concurrent.CountDownLatch; 9 | 10 | /** 11 | * a simple listener with the purpose to check the payload received 12 | */ 13 | @Component 14 | public class ListenerPayloadChecker { 15 | 16 | private CountDownLatch latch = new CountDownLatch(1); 17 | private ConsumerRecord consumerRecord = null; 18 | 19 | @KafkaListener(topics = "${topic.name.order}", groupId = "test") 20 | private void receive(ConsumerRecord consumerRecord) { 21 | setConsumerRecord(consumerRecord); 22 | latch.countDown(); 23 | } 24 | 25 | public CountDownLatch getLatch() { 26 | return latch; 27 | } 28 | 29 | public ConsumerRecord getConsumerRecord() { 30 | return consumerRecord; 31 | } 32 | 33 | public void setConsumerRecord(ConsumerRecord consumerRecord) { 34 | this.consumerRecord = consumerRecord; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/producer/KafkaProducer.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.producer; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.kafka.core.KafkaTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Slf4j 13 | public class KafkaProducer { 14 | 15 | 16 | private final KafkaTemplate kafkaTemplate; 17 | private final String orderTopic; 18 | private final String notificationTopic; 19 | 20 | public KafkaProducer(KafkaTemplate kafkaTemplate, 21 | @Value("${topic.name.order}") String orderTopic, 22 | @Value("${topic.name.notification}") String notificationTopic) { 23 | this.kafkaTemplate = kafkaTemplate; 24 | this.orderTopic = orderTopic; 25 | this.notificationTopic = notificationTopic; 26 | } 27 | 28 | public void sendOrder(String key, Order payload) { 29 | log.debug("sending to order topic={}, key={}, topic={}", payload, key, orderTopic); 30 | kafkaTemplate.send(orderTopic, key, payload); 31 | } 32 | public void sendNotification(String key, Order payload) { 33 | log.debug("sending to notification topic={}, key={}, topic={}", payload, key, notificationTopic); 34 | kafkaTemplate.send(notificationTopic, key, payload); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/controller/OrderControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.controller; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.kafka.annotation.EnableKafkaStreams; 11 | import org.springframework.test.annotation.DirtiesContext; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | import java.util.UUID; 15 | 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | import static org.junit.jupiter.api.Assertions.*; 19 | 20 | @SpringBootTest 21 | @AutoConfigureMockMvc 22 | class OrderControllerTest { 23 | 24 | @Autowired 25 | private MockMvc mvc; 26 | private String url = "/api/v1/order?o=3&i=2"; 27 | 28 | static { 29 | System.setProperty("spring.kafka.streams.state-dir", "/tmp/kafka-streams/"+ UUID.randomUUID()); 30 | } 31 | 32 | 33 | @Test 34 | void sendOrders() throws Exception { 35 | mvc.perform(get(url) 36 | .contentType("application/json")) 37 | .andExpect(status().isOk()); 38 | 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /payment/src/main/java/com/sylleryum/payment/service/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment.service; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.common.entity.Order; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | @Slf4j 10 | public class PaymentService { 11 | 12 | private final GlobalConfigs globalConfigs; 13 | 14 | public PaymentService(GlobalConfigs globalConfigs) { 15 | this.globalConfigs = globalConfigs; 16 | } 17 | 18 | /** 19 | * process payment, result will be based on {@link GlobalConfigs} 20 | * statusPayment (if success is set to partial, result will be random) 21 | * @param order 22 | * @return if payment was processed successfully, true, else false 23 | */ 24 | public boolean newPayment(Order order){ 25 | boolean result; 26 | System.out.println("******Mock payment processed "+order.getOrderPrice()); 27 | log.debug("Mock payment processed {}",order.getOrderPrice()); 28 | result = globalConfigs.statusPayment() == globalConfigs.SUCCESS; 29 | 30 | return result; 31 | } 32 | 33 | /** 34 | * process rollback 35 | * @param order 36 | * @return if payment was processed successfully, true, else false 37 | */ 38 | public boolean rollbackPayment(Order order){ 39 | System.out.println("******Mock payment refunded "+order.getOrderPrice()); 40 | log.debug("Mock payment refund {}",order.getOrderPrice()); 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/util/DbDataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.util; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.stock.entity.StockItem; 5 | import com.sylleryum.stock.service.StockService; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.data.mongodb.core.MongoTemplate; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class DbDataInitializer { 12 | 13 | private final StockService stockService; 14 | private final MongoTemplate mongoTemplate; 15 | private final String collectionName; 16 | private final GlobalConfigs globalConfigs; 17 | private final long documentAmount; 18 | 19 | public DbDataInitializer(StockService stockService, 20 | MongoTemplate mongoTemplate, 21 | @Value("${mongodb.collection.name}") String collectionName, 22 | GlobalConfigs globalConfigs, @Value("${initdb.amount}") long documentAmount) { 23 | this.stockService = stockService; 24 | this.mongoTemplate = mongoTemplate; 25 | this.collectionName = collectionName; 26 | this.globalConfigs = globalConfigs; 27 | this.documentAmount = documentAmount; 28 | } 29 | 30 | public void initDbData() { 31 | mongoTemplate.dropCollection(collectionName); 32 | 33 | long itemQuantity = globalConfigs.statusStock() == globalConfigs.SUCCESS ? 99999L : 10L; 34 | for (int i = 1; i <= documentAmount; i++) { 35 | stockService.save(StockItem.builder().description("item " + i).quantity(itemQuantity).itemNumber(globalConfigs.itemPrefix() + i).build()); 36 | } 37 | 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /payment/src/main/java/com/sylleryum/payment/service/OrderPaymentManagementService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment.service; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import com.sylleryum.payment.producer.KafkaProducer; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @Slf4j 11 | public class OrderPaymentManagementService { 12 | 13 | private final PaymentService paymentService; 14 | private final KafkaProducer kafkaProducer; 15 | 16 | public OrderPaymentManagementService(PaymentService paymentService, KafkaProducer kafkaProducer) { 17 | this.paymentService = paymentService; 18 | this.kafkaProducer = kafkaProducer; 19 | } 20 | 21 | public Order processPayment(Order order){ 22 | if (order.getPaymentStatus().equalsIgnoreCase(OrderStatus.NEW)) { 23 | return newOrder(order); 24 | } else if (order.getPaymentStatus().equalsIgnoreCase(OrderStatus.ROLLBACK)){ 25 | paymentService.rollbackPayment(order); 26 | } 27 | return order; 28 | } 29 | 30 | private Order newOrder(Order order) { 31 | log.debug("new payment request {}", order.getOrderNumber()); 32 | boolean result = paymentService.newPayment(order); 33 | if (result){ 34 | log.debug("payment accepted {}", order.getOrderNumber()); 35 | 36 | order.setPaymentStatus(OrderStatus.SUCCESS); 37 | } else { 38 | log.debug("payment denied {}", order.getOrderNumber()); 39 | order.setPaymentStatus(OrderStatus.FAILED); 40 | order.setPaymentStatusReason("Payment denied"); 41 | } 42 | kafkaProducer.send(order.getOrderNumber(), order); 43 | return order; 44 | } 45 | } -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/StockApplication.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.stock.service.OrderManagementService; 5 | import com.sylleryum.stock.service.StockService; 6 | import com.sylleryum.stock.util.DbDataInitializer; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.kafka.annotation.EnableKafka; 13 | import org.springframework.kafka.annotation.KafkaListener; 14 | 15 | @SpringBootApplication 16 | @ComponentScan(basePackages = "com.sylleryum") 17 | @EnableKafka 18 | @Slf4j 19 | public class StockApplication implements CommandLineRunner { 20 | 21 | private final DbDataInitializer dbDataInitializer; 22 | private final StockService stockService; 23 | private final OrderManagementService orderManagementService; 24 | 25 | public StockApplication(DbDataInitializer dbDataInitializer, StockService stockService, OrderManagementService orderManagementService) { 26 | this.dbDataInitializer = dbDataInitializer; 27 | this.stockService = stockService; 28 | this.orderManagementService = orderManagementService; 29 | } 30 | 31 | 32 | public static void main(String[] args) { 33 | SpringApplication.run(StockApplication.class, args); 34 | } 35 | 36 | @Override 37 | public void run(String... args) { 38 | dbDataInitializer.initDbData(); 39 | } 40 | 41 | @KafkaListener(topics = "${topic.name.order}") 42 | private void listener(Order order) { 43 | 44 | orderManagementService.processStockRequest(order); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /notification/src/main/java/com/sylleryum/notifications/service/NotificationService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.notifications.service; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Service 13 | public class NotificationService { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(NotificationService.class); 16 | 17 | public List sendNotification(Order order) { 18 | if (order.getStockStatus().equalsIgnoreCase(OrderStatus.SUCCESS) && order.getPaymentStatus().equalsIgnoreCase(OrderStatus.SUCCESS)) { 19 | successNotification(order); 20 | return List.of(); 21 | } 22 | return failureNotification(order); 23 | } 24 | 25 | private void successNotification(Order order) { 26 | LOG.info("success for order {}", order.getOrderNumber()); 27 | System.out.println("***Notification sent: your order " + order.getOrderNumber() + " has been processed and is ready to be shipped..."); 28 | } 29 | 30 | private List failureNotification(Order order) { 31 | List failureReasons = new ArrayList<>(); 32 | LOG.warn("Order failed {}", order); 33 | if (order.getStockStatus().equalsIgnoreCase(OrderStatus.FAILED)) 34 | failureReasons.add(order.getStockStatusReason()); 35 | if (order.getPaymentStatus().equalsIgnoreCase(OrderStatus.FAILED)) { 36 | failureReasons.add(order.getPaymentStatusReason()); 37 | } 38 | String singularOrPluralReason = failureReasons.size() > 1 ? "reasons" : "reason"; 39 | 40 | System.out.println("*********Order " + order.getOrderNumber() + " has failed for the following " + singularOrPluralReason + " :" + failureReasons); 41 | return failureReasons; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /resources/docker files/test containers/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo: 5 | image: mongo 6 | environment: 7 | MONGO_INITDB_ROOT_USERNAME: root 8 | MONGO_INITDB_ROOT_PASSWORD: Mongo2022! 9 | ports: 10 | - "27017:27017" 11 | volumes: 12 | - mongodata:/data/db 13 | networks: 14 | - mongo-compose-network 15 | 16 | postgres-compose: 17 | image: postgres 18 | environment: 19 | POSTGRES_PASSWORD: "admin123" 20 | POSTGRES_DB: "kafka" 21 | ports: 22 | - "5432:5432" 23 | volumes: 24 | - pgdata:/var/lib/postgresql/data:z 25 | networks: 26 | - postgres-compose-network 27 | 28 | zookeeper: 29 | image: confluentinc/cp-zookeeper:5.1.2 30 | restart: always 31 | environment: 32 | ZOOKEEPER_SERVER_ID: 1 33 | ZOOKEEPER_CLIENT_PORT: "2181" 34 | ZOOKEEPER_TICK_TIME: "2000" 35 | ZOOKEEPER_SERVERS: "zookeeper:22888:23888" 36 | ports: 37 | - "2181:2181" 38 | 39 | kafka1: 40 | image: confluentinc/cp-kafka:7.0.0 41 | container_name: kafka 42 | depends_on: 43 | - zookeeper 44 | ports: 45 | - "29092:29092" 46 | environment: 47 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 48 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 49 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 50 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092,PLAINTEXT_HOST://localhost:29092 51 | KAFKA_ADVERTISED_HOST_NAME: kafka1 52 | KAFKA_BROKER_ID: 1 53 | KAFKA_BROKER_RACK: "r1" 54 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 55 | KAFKA_DELETE_TOPIC_ENABLE: "true" 56 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" 57 | KAFKA_SCHEMA_REGISTRY_URL: "schemaregistry:8085" 58 | KAFKA_JMX_PORT: 9991 59 | 60 | networks: 61 | mongo-compose-network: 62 | driver: bridge 63 | postgres-compose-network: 64 | driver: bridge 65 | 66 | volumes: 67 | mongodata: 68 | pgdata: -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/service/StockService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.service; 2 | 3 | import com.sylleryum.stock.entity.StockItem; 4 | import com.sylleryum.stock.exceptions.StockException; 5 | import com.sylleryum.stock.repository.StockRepository; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | @Service 13 | @Slf4j 14 | public class StockService { 15 | 16 | private final StockRepository stockRepository; 17 | 18 | public StockService(StockRepository stockRepository) { 19 | this.stockRepository = stockRepository; 20 | } 21 | 22 | public List findAll() { 23 | return stockRepository.findAll(); 24 | } 25 | public S save(S entity) { 26 | return stockRepository.save(entity); 27 | } 28 | 29 | public List saveAll(Iterable entities) { 30 | return stockRepository.saveAll(entities); 31 | } 32 | 33 | public long count() { 34 | return stockRepository.count(); 35 | } 36 | 37 | /** 38 | * find {@link StockItem} based on itemNumbers 39 | * @param itemNumbers 40 | * @param matchAll if true, returns list of {@link StockItem} only if all itemNumbers passed are found, else, returns an empty list 41 | * @return 42 | */ 43 | public List findByItemNumbers(List itemNumbers, boolean matchAll) { 44 | if (itemNumbers.isEmpty()) { 45 | log.error("no item number passed"); 46 | throw new StockException("no Item number passed"); 47 | } 48 | List itemList = stockRepository.findByItemNumberIn(itemNumbers); 49 | if (!matchAll) return itemList; 50 | 51 | if (itemList.size()==itemNumbers.size()) return itemList; 52 | log.debug("no StockItem found for itemBumbers {}", itemNumbers); 53 | return List.of(); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/util/OrderConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.util; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.order.entity.ItemDAO; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 15 | 16 | @SpringBootTest 17 | class OrderConverterTest { 18 | 19 | @Autowired 20 | OrderConverter orderConverter; 21 | 22 | @Test 23 | void orderPojoToKafka_convertOrderDAOtoOrder_newOrder() { 24 | //create orderDAO 25 | ItemDAO itemDAO = ItemDAO.builder().itemNumber(UUID.randomUUID().toString()).id(1L).build(); 26 | OrderDAO orderDAO = new OrderDAO(UUID.randomUUID().toString(), List.of(itemDAO),1.99); 27 | itemDAO.setOrder(orderDAO); 28 | 29 | //convert orderDAO to order from common package 30 | Order order = orderConverter.orderDaoToKafka(orderDAO); 31 | 32 | assertThat(order).isNotNull(); 33 | assertThat(order).isInstanceOf(Order.class); 34 | } 35 | 36 | @Test 37 | void listOrderPojoToKafka_convertListOfOrderDAOtoOrder_newListOfOrder() { 38 | List listOrderDAO = new ArrayList<>(); 39 | ItemDAO itemDAO = ItemDAO.builder().itemNumber(UUID.randomUUID().toString()).id(1L).build(); 40 | OrderDAO orderDAO = new OrderDAO(UUID.randomUUID().toString(), List.of(itemDAO),1.99); 41 | itemDAO.setOrder(orderDAO); 42 | listOrderDAO.add(orderDAO); 43 | 44 | List listOrder = orderConverter.listOrderDaoToKafka(listOrderDAO); 45 | 46 | //size of order 47 | assertThat(listOrder.size()).isEqualTo(1); 48 | //size of items inside order 49 | assertThat(listOrder.get(0).getItems().size()).isEqualTo(1); 50 | } 51 | } -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/service/OrderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.service; 2 | 3 | import com.sylleryum.order.entity.ItemDAO; 4 | import com.sylleryum.order.entity.OrderDAO; 5 | import com.sylleryum.order.exception.OrderException; 6 | import org.junit.jupiter.api.*; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.UUID; 14 | 15 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 16 | 17 | @SpringBootTest 18 | class OrderServiceTest { 19 | 20 | @Autowired 21 | OrderService orderService; 22 | static OrderDAO orderDAO; 23 | static ItemDAO itemDAO; 24 | 25 | static { 26 | System.setProperty("spring.kafka.streams.state-dir", "/tmp/kafka-streams/" + UUID.randomUUID()); 27 | } 28 | 29 | @BeforeAll 30 | static void beforeAll() { 31 | itemDAO = ItemDAO.builder().itemNumber(UUID.randomUUID().toString()).build(); 32 | orderDAO = new OrderDAO(UUID.randomUUID().toString(), List.of(itemDAO), 1.99); 33 | itemDAO.setOrder(orderDAO); 34 | 35 | } 36 | 37 | @Test 38 | void save_persistOrder_persisted() { 39 | OrderDAO save = orderService.save(orderDAO); 40 | 41 | assertThat(save).isNotNull(); 42 | 43 | } 44 | 45 | @Test 46 | void findByOrderNumber_isFound() { 47 | String orderNumberTest = UUID.randomUUID().toString(); 48 | orderDAO.setOrderNumber(orderNumberTest); 49 | orderService.save(orderDAO); 50 | 51 | Optional orderFound = orderService.findByOrderNumber(orderNumberTest); 52 | assertThat(orderFound.get().getId()).isNotNull(); 53 | } 54 | 55 | @Test 56 | void findByOrderNumber_notFound_optionalIsEmpty() { 57 | String orderNumberNotfound = "notfound"; 58 | 59 | Optional orderResult = orderService.findByOrderNumber(orderNumberNotfound); 60 | assertThat(orderResult.isPresent()).isFalse(); 61 | } 62 | } -------------------------------------------------------------------------------- /payment/src/test/java/com/sylleryum/payment/service/PaymentServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment.service; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.common.entity.Item; 5 | import com.sylleryum.common.entity.Order; 6 | import org.junit.jupiter.api.BeforeAll; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.mockito.Mockito; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 18 | 19 | @SpringBootTest 20 | class PaymentServiceTest { 21 | 22 | @Autowired 23 | PaymentService paymentService; 24 | @MockBean 25 | GlobalConfigs globalConfigs; 26 | static Order orderTest; 27 | 28 | @BeforeAll 29 | static void beforeAll() { 30 | Item item = new Item("test" + 9, 5); 31 | Item item1 = new Item("test" + 5, 3); 32 | Item item2 = new Item("test" + 2, 8); 33 | orderTest = new Order( 34 | "orderNumber", List.of(item, item1, item2), 3L); 35 | } 36 | 37 | @Test 38 | void newPayment_successPayment_true() { 39 | Mockito.when(globalConfigs.statusPayment()).thenReturn(1); 40 | boolean isPaymentCompleted = paymentService.newPayment(orderTest); 41 | 42 | assertThat(isPaymentCompleted).isTrue(); 43 | } 44 | 45 | @Test 46 | void newPayment_successPayment_False() { 47 | Mockito.when(globalConfigs.statusPayment()).thenReturn(-1); 48 | boolean isPaymentCompleted = paymentService.newPayment(orderTest); 49 | 50 | assertThat(isPaymentCompleted).isFalse(); 51 | } 52 | 53 | @Test 54 | void rollbackPayment() { 55 | Mockito.when(globalConfigs.statusPayment()).thenReturn(1); 56 | 57 | boolean rollbackPaymentResult = paymentService.rollbackPayment(orderTest); 58 | assertThat(rollbackPaymentResult).isTrue(); 59 | } 60 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.4 9 | 10 | 11 | com.sylleryum 12 | order-microservices 13 | 0.0.1-SNAPSHOT 14 | order-microservices 15 | order-microservices 16 | 17 | pom 18 | 19 | 20 | 11 21 | 22 | 23 | 24 | order 25 | stock 26 | payment 27 | notification 28 | common 29 | 30 | 31 | 32 | 33 | org.jacoco 34 | jacoco-maven-plugin 35 | 0.8.5 36 | 37 | 38 | 39 | default-prepare-agent 40 | 41 | prepare-agent 42 | 43 | 44 | 45 | 46 | 47 | report 48 | test 49 | 50 | report 51 | 52 | 53 | ../target 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.controller; 2 | 3 | import com.sylleryum.order.entity.OrderDAO; 4 | import com.sylleryum.order.entity.OrderPojoWrapper; 5 | import com.sylleryum.order.util.OrderGenerator; 6 | import com.sylleryum.common.entity.Order; 7 | import com.sylleryum.order.producer.KafkaProducer; 8 | import com.sylleryum.order.service.OrderService; 9 | import com.sylleryum.order.util.OrderConverter; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.kafka.core.KafkaTemplate; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.List; 18 | import java.util.Optional; 19 | 20 | @RestController 21 | @RequestMapping("api/v1") 22 | public class OrderController { 23 | 24 | private final KafkaTemplate kafkaTemplate; 25 | private final OrderService orderService; 26 | private final OrderConverter orderConverter; 27 | private final OrderGenerator orderGenerator; 28 | private final KafkaProducer kafkaProducer; 29 | 30 | public OrderController(KafkaTemplate kafkaTemplate, 31 | OrderService orderService, 32 | OrderConverter orderConverter, OrderGenerator orderGenerator, KafkaProducer kafkaProducer) { 33 | this.kafkaTemplate = kafkaTemplate; 34 | this.orderService = orderService; 35 | this.orderConverter = orderConverter; 36 | this.orderGenerator = orderGenerator; 37 | this.kafkaProducer = kafkaProducer; 38 | } 39 | 40 | @GetMapping("/order") 41 | public ResponseEntity sendOrders(@RequestParam(name = "o") Optional orders, 42 | @RequestParam(name = "i") Optional items) { 43 | List orderDAOList = orderGenerator.generateNewOrderDAOs(orders, items); 44 | 45 | orderService.saveAll(orderDAOList); 46 | 47 | List orderList = orderConverter.listOrderDaoToKafka(orderDAOList); 48 | orderList.forEach(order -> 49 | kafkaProducer.sendOrder(order.getOrderNumber(), order)); 50 | 51 | return ResponseEntity.ok(new OrderPojoWrapper(orderList)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/producer/KafkaProducerTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.producer; 2 | 3 | import com.sylleryum.common.entity.Item; 4 | import com.sylleryum.common.entity.Order; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import com.sylleryum.order.testUtil.ListenerPayloadChecker; 7 | import com.sylleryum.order.util.OrderConverter; 8 | import com.sylleryum.order.util.OrderGenerator; 9 | import org.apache.kafka.clients.consumer.ConsumerRecord; 10 | import org.junit.Ignore; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.kafka.test.context.EmbeddedKafka; 15 | import org.springframework.test.annotation.DirtiesContext; 16 | 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.UUID; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 23 | 24 | @SpringBootTest 25 | @DirtiesContext 26 | class KafkaProducerTest { 27 | 28 | @Autowired 29 | private KafkaProducer kafkaProducer; 30 | @Autowired 31 | ListenerPayloadChecker listenerPayloadChecker; 32 | @Autowired 33 | OrderGenerator orderGenerator; 34 | @Autowired 35 | OrderConverter orderConverter; 36 | 37 | static { 38 | System.setProperty("spring.kafka.streams.state-dir", "/tmp/kafka-streams/"+ UUID.randomUUID()); 39 | } 40 | 41 | @Test 42 | void send_eventProduced_isSent() throws InterruptedException { 43 | // String orderID = "orderkey1"; 44 | // String itemID = "itemkey1"; 45 | // 46 | // Order order = new Order(orderID, 2.99); 47 | // Item item = new Item(itemID, 4); 48 | // order.setItems(List.of(item)); 49 | // 50 | // kafkaProducer.sendOrder(order.getOrderNumber(), order); 51 | // listenerPayloadChecker.getLatch().await(10000, TimeUnit.MILLISECONDS); 52 | // ConsumerRecord result = listenerPayloadChecker.getConsumerRecord(); 53 | // List items = result.value().getItems(); 54 | // 55 | // assertThat(result).isNotNull(); 56 | // assertThat(items).isNotNull(); 57 | // System.out.println(); 58 | } 59 | 60 | @Test 61 | @Ignore 62 | void generalTests() { 63 | List orderDAOList = orderGenerator.generateNewOrderDAOs(Optional.of(2L), Optional.of(1L)); 64 | List orders = orderConverter.listOrderDaoToKafka(orderDAOList); 65 | orders.forEach(order -> kafkaProducer.sendOrder(order.getOrderNumber(),order)); 66 | } 67 | } -------------------------------------------------------------------------------- /notification/src/test/java/com/sylleryum/notifications/service/NotificationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.notifications.service; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 14 | 15 | @SpringBootTest 16 | class NotificationServiceTest { 17 | 18 | @Autowired 19 | NotificationService notificationService; 20 | Order order; 21 | static String STOCK_ERROR = "stock error"; 22 | static String PAYMENT_ERROR = "payment error"; 23 | 24 | 25 | @BeforeEach 26 | void setUp() { 27 | order = new Order("itemnumber", List.of(), 1, null, null, null, null); 28 | } 29 | 30 | @Test 31 | void sendNotification_orderIsSuccess_noFailure() { 32 | order.setStockStatus(OrderStatus.SUCCESS); 33 | order.setPaymentStatus(OrderStatus.SUCCESS); 34 | List failureReasons = notificationService.sendNotification(order); 35 | 36 | assertThat(failureReasons.isEmpty()).isTrue(); 37 | } 38 | 39 | @Test 40 | void sendNotification_stockIsFailure_isStockError() { 41 | order.setStockStatus(OrderStatus.FAILED); 42 | order.setStockStatusReason(STOCK_ERROR); 43 | order.setPaymentStatus(OrderStatus.SUCCESS); 44 | List failureReasons = notificationService.sendNotification(order); 45 | 46 | assertThat(failureReasons.get(0)).isEqualTo(STOCK_ERROR); 47 | } 48 | 49 | @Test 50 | void sendNotification_paymentIsFailure_isPaymentError() { 51 | order.setStockStatus(OrderStatus.SUCCESS); 52 | order.setPaymentStatus(OrderStatus.FAILED); 53 | order.setPaymentStatusReason(PAYMENT_ERROR); 54 | List failureReasons = notificationService.sendNotification(order); 55 | 56 | assertThat(failureReasons.get(0)).isEqualTo(PAYMENT_ERROR); 57 | } 58 | 59 | @Test 60 | void sendNotification_paymentAndStockIsFailure_isPaymentErrorAndStockError() { 61 | order.setStockStatus(OrderStatus.FAILED); 62 | order.setStockStatusReason(STOCK_ERROR); 63 | order.setPaymentStatus(OrderStatus.FAILED); 64 | order.setPaymentStatusReason(PAYMENT_ERROR); 65 | List failureReasons = notificationService.sendNotification(order); 66 | 67 | assertThat(failureReasons.toString()).contains(STOCK_ERROR); 68 | assertThat(failureReasons.toString()).contains(PAYMENT_ERROR); 69 | } 70 | } -------------------------------------------------------------------------------- /payment/src/test/java/com/sylleryum/payment/service/OrderManagementServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.payment.service; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.common.entity.Item; 5 | import com.sylleryum.common.entity.Order; 6 | import com.sylleryum.common.util.OrderStatus; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.Mockito; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 17 | 18 | @SpringBootTest 19 | class OrderManagementServiceTest { 20 | 21 | @Autowired 22 | OrderPaymentManagementService orderManagementService; 23 | @MockBean 24 | GlobalConfigs globalConfigs; 25 | 26 | @Test 27 | void processPayment_newOrder_success() { 28 | Mockito.when(globalConfigs.statusPayment()).thenReturn(1); 29 | int itemQuantity=10; 30 | List itemList = new ArrayList<>(); 31 | for (int i=1;i<=itemQuantity;i++) { 32 | itemList.add(new Item(globalConfigs.itemPrefix()+i, 5)); 33 | } 34 | Order order = new Order( 35 | "orderNumber", itemList,3L); 36 | 37 | Order result = orderManagementService.processPayment(order); 38 | assertThat(result.getPaymentStatus()).isEqualTo(OrderStatus.SUCCESS); 39 | } 40 | 41 | @Test 42 | void processPayment_newOrder_failed() { 43 | Mockito.when(globalConfigs.statusPayment()).thenReturn(-1); 44 | int itemQuantity=10; 45 | List itemList = new ArrayList<>(); 46 | for (int i=1;i<=itemQuantity;i++) { 47 | itemList.add(new Item(globalConfigs.itemPrefix()+i, 5)); 48 | } 49 | Order order = new Order( 50 | "orderNumber", itemList,3L); 51 | 52 | Order result = orderManagementService.processPayment(order); 53 | assertThat(result.getPaymentStatus()).isEqualTo(OrderStatus.FAILED); 54 | } 55 | 56 | @Test 57 | void processPayment_rollback_true() { 58 | Mockito.when(globalConfigs.statusPayment()).thenReturn(1); 59 | int itemQuantity=2; 60 | List itemList = new ArrayList<>(); 61 | for (int i=1;i<=itemQuantity;i++) { 62 | itemList.add(new Item(globalConfigs.itemPrefix()+i, 3)); 63 | } 64 | Order order = new Order( 65 | "orderNumber", itemList,3L); 66 | order.setPaymentStatus(OrderStatus.ROLLBACK); 67 | Order processPayment = orderManagementService.processPayment(order); 68 | 69 | assertThat(processPayment.getPaymentStatus()).isEqualTo(OrderStatus.ROLLBACK); 70 | } 71 | } -------------------------------------------------------------------------------- /common/src/main/resources/shared.properties: -------------------------------------------------------------------------------- 1 | #1=(default) all ordered items are available in stock microservice (complete success) 2 | #-1=no ordered items are available in stock microservice (complete failure) 3 | order.status.stock=1 4 | 5 | #1=(default) all ordered payments are approved in payment microservice (complete success) 6 | #-1=no ordered payments are approved in payment microservice (complete failure) 7 | order.status.payment=1 8 | 9 | #for the sake of this example, new orders and items from stock microservice will generate item number based 10 | # on the below prefix (in a real case scenario this naive strategy shouldn't be used) 11 | item.number.prefix=itemnumber 12 | 13 | #max items allowed when generating orders (total of items rather than items per order) 14 | max.items.allowed=1000 15 | 16 | spring.kafka.bootstrap-servers=localhost:29092 17 | topic.name.order=t_order 18 | topic.name.payment=t_payment 19 | topic.name.stock=t_stock 20 | topic.name.notification=t_notification 21 | 22 | #time the Stream innerjoin window will be open in milliseconds 23 | #to receive the answer of order processing from other services 24 | kafka.join.window.duration.ms=120000 25 | 26 | ###Producer 27 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 28 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 29 | spring.kafka.producer.properties.enable.idempotence=true 30 | spring.kafka.producer.properties.acks=-1 31 | spring.kafka.producer.properties.retries=11111 32 | 33 | ###Stream 34 | spring.kafka.streams.bootstrap-servers=localhost:29092 35 | spring.kafka.streams.properties.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde 36 | spring.kafka.streams.properties.default.value.serde=org.springframework.kafka.support.serializer.JsonSerde 37 | spring.kafka.streams.properties.spring.json.trusted.packages="*" 38 | spring.kafka.streams.state-dir=/tmp/streams/1 39 | spring.kafka.streams.properties.processing.guarantee=at_least_once 40 | spring.kafka.streams.cleanup.on-shutdown=true 41 | 42 | ###Consumer 43 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 44 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 45 | spring.kafka.consumer.properties.spring.json.trusted.packages=* 46 | spring.kafka.consumer.auto-offset-reset=earliest 47 | spring.kafka.consumer.enable-auto-commit=true 48 | 49 | #As topics are created with 3 partitions in the order service, 50 | # this will avoid another service to create and auto assign a 1 partition only to itself 51 | spring.kafka.consumer.properties.allow.auto.create.topics=false 52 | 53 | ###logging 54 | logging.level.com.sylleryum=DEBUG 55 | 56 | ###Schema registry AVRO example 57 | #spring.kafka.properties.schema.registry.url=http://0.0.0.0:8085 58 | #auto.create.topics.enable=true 59 | #spring.kafka.properties.specific.avro.reader=true 60 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/config/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.config; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import org.apache.kafka.clients.admin.NewTopic; 5 | import org.apache.kafka.clients.producer.ProducerConfig; 6 | import org.apache.kafka.streams.KafkaStreams; 7 | import org.apache.kafka.streams.StreamsBuilder; 8 | import org.apache.kafka.streams.StreamsConfig; 9 | import org.springframework.beans.factory.ObjectProvider; 10 | import org.springframework.beans.factory.UnsatisfiedDependencyException; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.kafka.annotation.KafkaStreamsDefaultConfiguration; 17 | import org.springframework.kafka.config.KafkaStreamsConfiguration; 18 | import org.springframework.kafka.config.StreamsBuilderFactoryBean; 19 | import org.springframework.kafka.config.StreamsBuilderFactoryBeanConfigurer; 20 | import org.springframework.kafka.config.TopicBuilder; 21 | import org.springframework.kafka.core.*; 22 | 23 | import java.util.HashSet; 24 | import java.util.Map; 25 | import java.util.Properties; 26 | import java.util.Set; 27 | 28 | @Configuration 29 | public class KafkaConfig { 30 | 31 | private final KafkaProperties kafkaProperties; 32 | private final String orderTopic; 33 | private final String stockTopic; 34 | private final String paymentTopic; 35 | private final String notificationTopic; 36 | 37 | 38 | public KafkaConfig(KafkaProperties kafkaProperties, 39 | @Value("${topic.name.order}") String orderTopic, 40 | @Value("${topic.name.stock}") String stockTopic, 41 | @Value("${topic.name.payment}") String paymentTopic, 42 | @Value("${topic.name.notification}") String notificationTopic) { 43 | this.kafkaProperties = kafkaProperties; 44 | this.orderTopic = orderTopic; 45 | this.stockTopic = stockTopic; 46 | this.paymentTopic = paymentTopic; 47 | this.notificationTopic = notificationTopic; 48 | } 49 | 50 | 51 | @Bean 52 | public NewTopic createOrderTopic() { 53 | return TopicBuilder.name(orderTopic) 54 | .partitions(3) 55 | .compact() 56 | .build(); 57 | } 58 | 59 | @Bean 60 | public NewTopic createStockTopic() { 61 | return TopicBuilder.name(stockTopic) 62 | .partitions(3) 63 | .compact() 64 | .build(); 65 | } 66 | 67 | @Bean 68 | public NewTopic createPaymentTopic() { 69 | return TopicBuilder.name(paymentTopic) 70 | .partitions(3) 71 | .compact() 72 | .build(); 73 | } 74 | 75 | @Bean 76 | public NewTopic createTestTopic() { 77 | return TopicBuilder.name(notificationTopic) 78 | .partitions(3) 79 | .compact() 80 | .build(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/util/OrderGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.util; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.order.entity.ItemDAO; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import com.sylleryum.order.exception.OrderException; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.Mockito; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.boot.test.mock.mockito.MockBean; 15 | import org.springframework.test.annotation.DirtiesContext; 16 | 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.UUID; 20 | import java.util.stream.Collectors; 21 | import java.util.stream.Stream; 22 | 23 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 24 | 25 | 26 | @SpringBootTest 27 | class OrderGeneratorTest { 28 | 29 | @Autowired 30 | OrderGenerator orderGenerator; 31 | @MockBean 32 | GlobalConfigs globalConfigs; 33 | 34 | @BeforeEach 35 | void setUp() { 36 | Mockito.when(globalConfigs.itemPrefix()).thenReturn("itemnumber"); 37 | } 38 | static { 39 | System.setProperty("spring.kafka.streams.state-dir", "/tmp/kafka-streams/" + UUID.randomUUID()); 40 | } 41 | 42 | @Test 43 | void generateNewOrderDAOs_allOrdersGenerated_true() { 44 | var order = Optional.of(3L); 45 | var item = Optional.of(5L); 46 | 47 | List orderList = orderGenerator.generateNewOrderDAOs(order, item); 48 | assertThat(orderList.size()).isEqualTo(3); 49 | } 50 | 51 | @Test 52 | void generateNewOrderDAOs_globalConfigFailure_quantityAbove10() { 53 | Mockito.when(globalConfigs.statusStock()).thenReturn(-1); 54 | var order = Optional.of(3L); 55 | var item = Optional.of(5L); 56 | 57 | List orderList = orderGenerator.generateNewOrderDAOs(order, item); 58 | List itemQuantities = orderList.stream() 59 | .flatMap(orderDAO -> orderDAO.getItems().stream()) 60 | .map(ItemDAO::getQuantity) 61 | .collect(Collectors.toList()); 62 | itemQuantities.forEach( 63 | itemQuantity -> assertThat(itemQuantity).isGreaterThan(10)); 64 | } 65 | 66 | @Test 67 | void generateNewOrderDAOs_itemNumbersAreInSequence_true() { 68 | var order = Optional.of(5L); 69 | var item = Optional.of(3L); 70 | long lastItemNumber = (order.get() * item.get()); 71 | 72 | List orderList = orderGenerator.generateNewOrderDAOs(order, item); 73 | 74 | assertThat(orderList.get(4).getItems().get(2).getItemNumber()).isEqualTo(globalConfigs.itemPrefix()+lastItemNumber); 75 | } 76 | 77 | @Test 78 | void generateNewOrderDAOs_maxAmountInvalid_throwsOrderException() { 79 | var order = Optional.of(2L); 80 | var item = Optional.of(501L); 81 | 82 | Assertions.assertThrows(OrderException.class,() -> 83 | orderGenerator.generateNewOrderDAOs(order, item)); 84 | } 85 | } -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/OrderApplication.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.order.service.OrderManagementService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.kafka.common.serialization.Serde; 7 | import org.apache.kafka.common.serialization.Serdes; 8 | import org.apache.kafka.streams.StreamsBuilder; 9 | import org.apache.kafka.streams.kstream.Consumed; 10 | import org.apache.kafka.streams.kstream.JoinWindows; 11 | import org.apache.kafka.streams.kstream.KStream; 12 | import org.apache.kafka.streams.kstream.StreamJoined; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.boot.SpringApplication; 15 | import org.springframework.boot.autoconfigure.SpringBootApplication; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.ComponentScan; 18 | import org.springframework.kafka.annotation.EnableKafkaStreams; 19 | import org.springframework.kafka.support.serializer.JsonSerde; 20 | 21 | import java.time.Duration; 22 | import java.util.UUID; 23 | 24 | @SpringBootApplication 25 | @ComponentScan(basePackages = "com.sylleryum") 26 | @EnableKafkaStreams 27 | @Slf4j 28 | public class OrderApplication { 29 | 30 | 31 | public static void main(String[] args) { 32 | SpringApplication.run(OrderApplication.class, args); 33 | } 34 | 35 | private final String kafkaTopicStock; 36 | private final String kafkaTopicPayment; 37 | private final String kafkaTopicOrder; 38 | private final long joinWindow; 39 | private final OrderManagementService orderManagementService; 40 | 41 | 42 | public OrderApplication(@Value("${topic.name.stock}") String kafkaTopicStock, 43 | @Value("${topic.name.payment}") String kafkaTopicPayment, 44 | @Value("${topic.name.order}") String kafkaTopicOrder, 45 | @Value("${kafka.join.window.duration.ms}") long joinWindow, OrderManagementService orderManagementService) { 46 | this.kafkaTopicPayment = kafkaTopicPayment; 47 | this.kafkaTopicStock = kafkaTopicStock; 48 | this.kafkaTopicOrder = kafkaTopicOrder; 49 | this.joinWindow = joinWindow; 50 | this.orderManagementService = orderManagementService; 51 | System.setProperty("spring.kafka.streams.state-dir", "/tmp/kafka-streams/"+ UUID.randomUUID()); 52 | } 53 | 54 | @Bean 55 | public KStream kstreamOrder(StreamsBuilder builder) { 56 | Serde stringSerde = Serdes.String(); 57 | JsonSerde orderJsonSerde = new JsonSerde<>(Order.class); 58 | 59 | KStream orderStockStream = builder.stream(kafkaTopicStock, 60 | Consumed.with(stringSerde, orderJsonSerde)); 61 | KStream orderPaymentStream = builder.stream(kafkaTopicPayment, 62 | Consumed.with(stringSerde, orderJsonSerde)); 63 | 64 | 65 | orderStockStream.join(orderPaymentStream, orderManagementService::processOrder, JoinWindows.of(Duration.ofMillis(joinWindow)), 66 | StreamJoined.with(stringSerde, orderJsonSerde, orderJsonSerde)) 67 | .peek((s, entityJoin) -> log.debug("order joined: {}",entityJoin)) 68 | .to(kafkaTopicOrder); 69 | 70 | return orderStockStream; 71 | } 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/entity/OrderDAO.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonManagedReference; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import lombok.ToString; 6 | 7 | import javax.persistence.*; 8 | import java.util.List; 9 | 10 | @Entity 11 | @ToString 12 | public class OrderDAO { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 16 | private Long id; 17 | private String orderNumber; 18 | @OneToMany( 19 | mappedBy = "order", 20 | cascade = CascadeType.ALL) 21 | @JsonManagedReference 22 | private List items; 23 | private double orderPrice; 24 | @Column(nullable = false) 25 | private String stockStatus = OrderStatus.NEW; 26 | private String stockStatusReason; 27 | private String paymentStatus = OrderStatus.NEW; 28 | private String paymentStatusReason; 29 | 30 | public OrderDAO() { 31 | } 32 | 33 | public OrderDAO(String orderNumber, List items, double orderPrice) { 34 | this.orderNumber = orderNumber; 35 | this.items = items; 36 | this.orderPrice = orderPrice; 37 | } 38 | 39 | public OrderDAO(Long id, String orderNumber, List items, double orderPrice, String stockStatus, String stockStatusReason, String paymentStatus, String paymentStatusReason) { 40 | this.id = id; 41 | this.orderNumber = orderNumber; 42 | this.items = items; 43 | this.orderPrice = orderPrice; 44 | this.stockStatus = stockStatus; 45 | this.stockStatusReason = stockStatusReason; 46 | this.paymentStatus = paymentStatus; 47 | this.paymentStatusReason = paymentStatusReason; 48 | } 49 | 50 | public Long getId() { 51 | return id; 52 | } 53 | 54 | public void setId(Long id) { 55 | this.id = id; 56 | } 57 | 58 | public String getOrderNumber() { 59 | return orderNumber; 60 | } 61 | 62 | public void setOrderNumber(String orderNumber) { 63 | this.orderNumber = orderNumber; 64 | } 65 | 66 | public List getItems() { 67 | return items; 68 | } 69 | 70 | public void setItems(List items) { 71 | this.items = items; 72 | } 73 | 74 | public double getOrderPrice() { 75 | return orderPrice; 76 | } 77 | 78 | public void setOrderPrice(double orderPrice) { 79 | this.orderPrice = orderPrice; 80 | } 81 | 82 | public String getStockStatus() { 83 | return stockStatus; 84 | } 85 | 86 | public String getStockStatusReason() { 87 | return stockStatusReason; 88 | } 89 | 90 | public void setStockStatus(String status) { 91 | this.stockStatus = status; 92 | } 93 | 94 | public void setStockStatusReason(String statusReason) { 95 | this.stockStatusReason = statusReason; 96 | } 97 | 98 | public String getPaymentStatus() { 99 | return paymentStatus; 100 | } 101 | 102 | public void setPaymentStatus(String paymentStatus) { 103 | this.paymentStatus = paymentStatus; 104 | } 105 | 106 | public String getPaymentStatusReason() { 107 | return paymentStatusReason; 108 | } 109 | 110 | public void setPaymentStatusReason(String paymentReason) { 111 | this.paymentStatusReason = paymentReason; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo: 5 | image: mongo 6 | environment: 7 | MONGO_INITDB_ROOT_USERNAME: root 8 | MONGO_INITDB_ROOT_PASSWORD: Mongo2022! 9 | ports: 10 | - "27017:27017" 11 | volumes: 12 | - mongodata:/data/db 13 | networks: 14 | - mongo-compose-network 15 | 16 | mongo-express: 17 | image: mongo-express 18 | ports: 19 | - "8081:8081" 20 | environment: 21 | ME_CONFIG_MONGODB_URL: mongodb://root:Mongo2022!@mongo:27017/ 22 | ME_CONFIG_BASICAUTH_USERNAME: root 23 | ME_CONFIG_BASICAUTH_PASSWORD: Mongo2022! 24 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 25 | ME_CONFIG_MONGODB_ADMINPASSWORD: Mongo2022! 26 | ME_CONFIG_MONGODB_PORT: 27017 27 | links: 28 | - mongo 29 | depends_on: 30 | - mongo 31 | networks: 32 | - mongo-compose-network 33 | 34 | postgres-compose: 35 | image: postgres 36 | environment: 37 | POSTGRES_PASSWORD: "admin123" 38 | POSTGRES_DB: "kafka" 39 | ports: 40 | - "5432:5432" 41 | volumes: 42 | - pgdata:/var/lib/postgresql/data:z 43 | networks: 44 | - postgres-compose-network 45 | 46 | pgadmin-compose: 47 | image: dpage/pgadmin4 48 | environment: 49 | PGADMIN_DEFAULT_EMAIL: "test@test.com" 50 | PGADMIN_DEFAULT_PASSWORD: "PgAdmin123" 51 | ports: 52 | - "16543:80" 53 | depends_on: 54 | - postgres-compose 55 | networks: 56 | - postgres-compose-network 57 | 58 | zookeeper: 59 | image: confluentinc/cp-zookeeper:5.1.2 60 | restart: always 61 | environment: 62 | ZOOKEEPER_SERVER_ID: 1 63 | ZOOKEEPER_CLIENT_PORT: "2181" 64 | ZOOKEEPER_TICK_TIME: "2000" 65 | ZOOKEEPER_SERVERS: "zookeeper:22888:23888" 66 | ports: 67 | - "2181:2181" 68 | 69 | kafka1: 70 | image: confluentinc/cp-kafka:7.0.0 71 | container_name: kafka 72 | depends_on: 73 | - zookeeper 74 | ports: 75 | - "29092:29092" 76 | environment: 77 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 78 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 79 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 80 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092,PLAINTEXT_HOST://localhost:29092 81 | KAFKA_ADVERTISED_HOST_NAME: kafka1 82 | KAFKA_BROKER_ID: 1 83 | KAFKA_BROKER_RACK: "r1" 84 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 85 | KAFKA_DELETE_TOPIC_ENABLE: "true" 86 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" 87 | KAFKA_SCHEMA_REGISTRY_URL: "schemaregistry:8085" 88 | KAFKA_JMX_PORT: 9991 89 | 90 | schemaregistry: 91 | image: confluentinc/cp-schema-registry:5.1.2 92 | restart: always 93 | depends_on: 94 | - zookeeper 95 | environment: 96 | SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: "zookeeper:2181" 97 | SCHEMA_REGISTRY_HOST_NAME: schemaregistry 98 | SCHEMA_REGISTRY_LISTENERS: "http://0.0.0.0:8085" 99 | ports: 100 | - "8085:8085" 101 | kafdrop: 102 | image: obsidiandynamics/kafdrop 103 | restart: "no" 104 | ports: 105 | - "9000:9000" 106 | environment: 107 | KAFKA_BROKERCONNECT: "kafka1:9092" 108 | depends_on: 109 | - kafka1 110 | 111 | networks: 112 | mongo-compose-network: 113 | driver: bridge 114 | postgres-compose-network: 115 | driver: bridge 116 | 117 | volumes: 118 | mongodata: 119 | pgdata: -------------------------------------------------------------------------------- /resources/docker files/Run project locally/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo: 5 | image: mongo 6 | environment: 7 | MONGO_INITDB_ROOT_USERNAME: root 8 | MONGO_INITDB_ROOT_PASSWORD: Mongo2022! 9 | ports: 10 | - "27017:27017" 11 | volumes: 12 | - mongodata:/data/db 13 | networks: 14 | - mongo-compose-network 15 | 16 | mongo-express: 17 | image: mongo-express 18 | ports: 19 | - "8081:8081" 20 | environment: 21 | ME_CONFIG_MONGODB_URL: mongodb://root:Mongo2022!@mongo:27017/ 22 | ME_CONFIG_BASICAUTH_USERNAME: root 23 | ME_CONFIG_BASICAUTH_PASSWORD: Mongo2022! 24 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 25 | ME_CONFIG_MONGODB_ADMINPASSWORD: Mongo2022! 26 | ME_CONFIG_MONGODB_PORT: 27017 27 | links: 28 | - mongo 29 | depends_on: 30 | - mongo 31 | networks: 32 | - mongo-compose-network 33 | 34 | postgres-compose: 35 | image: postgres 36 | environment: 37 | POSTGRES_PASSWORD: "admin123" 38 | POSTGRES_DB: "kafka" 39 | ports: 40 | - "5432:5432" 41 | volumes: 42 | - pgdata:/var/lib/postgresql/data:z 43 | networks: 44 | - postgres-compose-network 45 | 46 | pgadmin-compose: 47 | image: dpage/pgadmin4 48 | environment: 49 | PGADMIN_DEFAULT_EMAIL: "test@test.com" 50 | PGADMIN_DEFAULT_PASSWORD: "PgAdmin123" 51 | ports: 52 | - "16543:80" 53 | depends_on: 54 | - postgres-compose 55 | networks: 56 | - postgres-compose-network 57 | 58 | zookeeper: 59 | image: confluentinc/cp-zookeeper:5.1.2 60 | restart: always 61 | environment: 62 | ZOOKEEPER_SERVER_ID: 1 63 | ZOOKEEPER_CLIENT_PORT: "2181" 64 | ZOOKEEPER_TICK_TIME: "2000" 65 | ZOOKEEPER_SERVERS: "zookeeper:22888:23888" 66 | ports: 67 | - "2181:2181" 68 | 69 | kafka1: 70 | image: confluentinc/cp-kafka:7.0.0 71 | container_name: kafka 72 | depends_on: 73 | - zookeeper 74 | ports: 75 | - "29092:29092" 76 | environment: 77 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 78 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 79 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 80 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092,PLAINTEXT_HOST://localhost:29092 81 | KAFKA_ADVERTISED_HOST_NAME: kafka1 82 | KAFKA_BROKER_ID: 1 83 | KAFKA_BROKER_RACK: "r1" 84 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 85 | KAFKA_DELETE_TOPIC_ENABLE: "true" 86 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" 87 | KAFKA_SCHEMA_REGISTRY_URL: "schemaregistry:8085" 88 | KAFKA_JMX_PORT: 9991 89 | 90 | schemaregistry: 91 | image: confluentinc/cp-schema-registry:5.1.2 92 | restart: always 93 | depends_on: 94 | - zookeeper 95 | environment: 96 | SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: "zookeeper:2181" 97 | SCHEMA_REGISTRY_HOST_NAME: schemaregistry 98 | SCHEMA_REGISTRY_LISTENERS: "http://0.0.0.0:8085" 99 | ports: 100 | - "8085:8085" 101 | kafdrop: 102 | image: obsidiandynamics/kafdrop 103 | restart: "no" 104 | ports: 105 | - "9000:9000" 106 | environment: 107 | KAFKA_BROKERCONNECT: "kafka1:9092" 108 | depends_on: 109 | - kafka1 110 | 111 | networks: 112 | mongo-compose-network: 113 | driver: bridge 114 | postgres-compose-network: 115 | driver: bridge 116 | 117 | volumes: 118 | mongodata: 119 | pgdata: -------------------------------------------------------------------------------- /notification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | order-microservices 7 | com.sylleryum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | notification 13 | 14 | 15 | 11 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-web 21 | 22 | 23 | 24 | org.springframework.kafka 25 | spring-kafka 26 | 27 | 28 | org.springframework.kafka 29 | spring-kafka-test 30 | test 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | 39 | com.sylleryum 40 | common 41 | 0.0.2-SNAPSHOT 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | com/sylleryum/order/entity/** 52 | com/sylleryum/common/entity/** 53 | 54 | 55 | org.jacoco 56 | jacoco-maven-plugin 57 | 0.8.5 58 | 59 | 60 | 61 | default-prepare-agent 62 | 63 | prepare-agent 64 | 65 | 66 | 67 | 68 | 69 | report 70 | test 71 | 72 | report 73 | 74 | 75 | ../target 76 | 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-maven-plugin 83 | 84 | 85 | sylleryum/${project.artifactId} 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /stock/src/test/java/com/sylleryum/stock/service/StockServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.service; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.stock.entity.StockItem; 5 | import com.sylleryum.stock.exceptions.StockException; 6 | import com.sylleryum.stock.util.DbDataInitializer; 7 | 8 | import org.junit.Ignore; 9 | import org.junit.jupiter.api.*; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.data.mongodb.core.MongoTemplate; 14 | 15 | import java.util.List; 16 | 17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 18 | 19 | @SpringBootTest 20 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 21 | class StockServiceTest { 22 | 23 | @Autowired 24 | StockService stockService; 25 | @Value("${initdb.amount}") 26 | long documentAmount; 27 | @Autowired 28 | MongoTemplate mongoTemplate; 29 | @Autowired 30 | GlobalConfigs globalConfigs; 31 | 32 | @Test 33 | @Ignore 34 | void test() { 35 | mongoTemplate.dropCollection("stock"); 36 | // currentStockItems.sort(Comparator.comparing(StockItem::getDescription)); 37 | StockItem stockItem = stockService.save(StockItem.builder().description("item test").quantity(10L).itemNumber("it").build()); 38 | StockItem stockItem1 = stockService.save(StockItem.builder().description("item c").quantity(10L).itemNumber("it").build()); 39 | StockItem stockItem2 = stockService.save(StockItem.builder().description("item g").quantity(10L).itemNumber("it").build()); 40 | List all = stockService.findAll(); 41 | // all.sort(Comparator.comparing(StockItem::getDescription)); 42 | System.out.println(); 43 | } 44 | 45 | @BeforeAll 46 | static void beforeAll(@Autowired DbDataInitializer dbDataInitializer) { 47 | dbDataInitializer.initDbData(); 48 | } 49 | 50 | @Test 51 | @Order(1) 52 | void count_equalDocumentAmount_true() { 53 | long count = stockService.count(); 54 | assertThat(count).isEqualTo(documentAmount); 55 | } 56 | 57 | @Test 58 | @Order(2) 59 | void findAll_equalDocumentAmount_true() { 60 | List all = stockService.findAll(); 61 | assertThat(all.size()).isEqualTo(documentAmount); 62 | 63 | } 64 | 65 | @Test 66 | void findByItemNumbers_allFound_true() { 67 | String item1 = globalConfigs.itemPrefix() + "3"; 68 | String item2 = globalConfigs.itemPrefix() + "9"; 69 | List itemNumbers = stockService.findByItemNumbers(List.of(item1, item2), true); 70 | assertThat(itemNumbers.size()).isEqualTo(2); 71 | } 72 | 73 | @Test 74 | void findByItemNumbers_allFound_false() { 75 | String item1 = globalConfigs.itemPrefix() + "3"; 76 | String item2 = globalConfigs.itemPrefix() + "Nope"; 77 | List itemNumbers = stockService.findByItemNumbers(List.of("i3", "nope"), true); 78 | assertThat(itemNumbers.isEmpty()).isTrue(); 79 | } 80 | 81 | @Test 82 | void findByItemNumbers_noArgsPassed_throwsStockException(){ 83 | 84 | Assertions.assertThrows(StockException.class, () -> 85 | stockService.findByItemNumbers(List.of(), false)); 86 | } 87 | 88 | @Test 89 | void save_persistEntity_idCreated() { 90 | StockItem stockItem = stockService.save(StockItem.builder().description("item test").quantity(10L).itemNumber("it").build()); 91 | assertThat(stockItem.getId()).isNotEmpty(); 92 | 93 | } 94 | } -------------------------------------------------------------------------------- /common/src/main/java/com/sylleryum/common/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.common.entity; 2 | 3 | 4 | import com.sylleryum.common.util.OrderStatus; 5 | 6 | import java.util.List; 7 | 8 | public class Order { 9 | 10 | private String orderNumber; 11 | private List items; 12 | private double orderPrice; 13 | private String stockStatus = OrderStatus.NEW; 14 | private String stockStatusReason; 15 | private String paymentStatus = OrderStatus.NEW; 16 | private String paymentStatusReason; 17 | //TODO change constructors for a builder 18 | public Order() { 19 | } 20 | 21 | public Order(String orderNumber) { 22 | this.orderNumber = orderNumber; 23 | } 24 | 25 | public Order(String orderNumber, double orderPrice) { 26 | this.orderNumber = orderNumber; 27 | this.orderPrice = orderPrice; 28 | } 29 | 30 | public Order(String orderNumber, List items, double orderPrice) { 31 | this.orderNumber = orderNumber; 32 | this.items = items; 33 | this.orderPrice = orderPrice; 34 | } 35 | 36 | public Order(String orderNumber, List items, double orderPrice, String stockStatus, String stockStatusReason, String paymentStatus, String paymentStatusReason) { 37 | this.orderNumber = orderNumber; 38 | this.items = items; 39 | this.orderPrice = orderPrice; 40 | this.stockStatus = stockStatus; 41 | this.stockStatusReason = stockStatusReason; 42 | this.paymentStatus = paymentStatus; 43 | this.paymentStatusReason = paymentStatusReason; 44 | } 45 | 46 | public String getOrderNumber() { 47 | return orderNumber; 48 | } 49 | 50 | public void setOrderNumber(String orderNumber) { 51 | this.orderNumber = orderNumber; 52 | } 53 | 54 | public List getItems() { 55 | return items; 56 | } 57 | 58 | public void setItems(List items) { 59 | this.items = items; 60 | } 61 | 62 | public double getOrderPrice() { 63 | return orderPrice; 64 | } 65 | 66 | public void setOrderPrice(double orderPrice) { 67 | this.orderPrice = orderPrice; 68 | } 69 | 70 | public String getStockStatus() { 71 | return stockStatus; 72 | } 73 | 74 | public void setStockStatus(String stockStatus) { 75 | this.stockStatus = stockStatus; 76 | } 77 | 78 | public String getStockStatusReason() { 79 | return stockStatusReason; 80 | } 81 | 82 | public void setStockStatusReason(String stockStatusReason) { 83 | this.stockStatusReason = stockStatusReason; 84 | } 85 | 86 | public String getPaymentStatus() { 87 | return paymentStatus; 88 | } 89 | 90 | public void setPaymentStatus(String paymentStatus) { 91 | this.paymentStatus = paymentStatus; 92 | } 93 | 94 | public String getPaymentStatusReason() { 95 | return paymentStatusReason; 96 | } 97 | 98 | public void setPaymentStatusReason(String paymentStatusReason) { 99 | this.paymentStatusReason = paymentStatusReason; 100 | } 101 | 102 | 103 | @Override 104 | public String toString() { 105 | return "Order{" + 106 | "orderNumber='" + orderNumber + '\'' + 107 | ", items=" + items + 108 | ", orderPrice=" + orderPrice + 109 | ", stockStatus='" + stockStatus + '\'' + 110 | ", stockStatusReason='" + stockStatusReason + '\'' + 111 | ", paymentStatus='" + paymentStatus + '\'' + 112 | ", paymentStatusReason='" + paymentStatusReason + '\'' + 113 | '}'; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.sylleryum 8 | common 9 | 0.0.2-SNAPSHOT 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.6.5 14 | 15 | 16 | common 17 | 18 | 19 | UTF-8 20 | 1.7 21 | 1.7 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | junit 37 | junit 38 | test 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | maven-clean-plugin 48 | 3.1.0 49 | 50 | 51 | 52 | maven-resources-plugin 53 | 3.0.2 54 | 55 | 56 | maven-compiler-plugin 57 | 3.8.0 58 | 59 | 60 | maven-surefire-plugin 61 | 2.22.1 62 | 63 | 64 | maven-jar-plugin 65 | 3.0.2 66 | 67 | 68 | maven-install-plugin 69 | 2.5.2 70 | 71 | 72 | maven-deploy-plugin 73 | 2.8.2 74 | 75 | 76 | 77 | maven-site-plugin 78 | 3.7.1 79 | 80 | 81 | maven-project-info-reports-plugin 82 | 3.0.0 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /stock/src/test/java/com/sylleryum/stock/service/OrderStockManagementServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.service; 2 | 3 | import com.sylleryum.common.config.GlobalConfigs; 4 | import com.sylleryum.common.entity.Item; 5 | import com.sylleryum.common.entity.Order; 6 | import com.sylleryum.common.util.OrderStatus; 7 | import com.sylleryum.stock.entity.StockItem; 8 | import com.sylleryum.stock.util.DbDataInitializer; 9 | import org.junit.jupiter.api.Test; 10 | import org.mockito.Mockito; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | 15 | import java.util.List; 16 | 17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 18 | 19 | @SpringBootTest 20 | class OrderStockManagementServiceTest { 21 | 22 | @Autowired 23 | OrderManagementService orderManagementService; 24 | @Autowired 25 | StockService stockService; 26 | @MockBean 27 | GlobalConfigs globalConfigs; 28 | @Autowired 29 | DbDataInitializer dbDataInitializer; 30 | 31 | @Test 32 | void processStockRequest_newOrder_AllItemsSuccess() { 33 | Mockito.when(globalConfigs.statusStock()).thenReturn(1); 34 | dbDataInitializer.initDbData(); 35 | 36 | Item item = new Item(globalConfigs.itemPrefix() + 9, 5); 37 | Item item1 = new Item(globalConfigs.itemPrefix() + 5, 3); 38 | Item item2 = new Item(globalConfigs.itemPrefix() + 2, 8); 39 | Order order = new Order( 40 | "orderNumber", List.of(item, item1, item2), 3L); 41 | 42 | Order orderResult = orderManagementService.processStockRequest(order); 43 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.SUCCESS 44 | ); 45 | } 46 | 47 | @Test 48 | void processStockRequest_newOrder_AllItemsfailed() { 49 | Mockito.when(globalConfigs.statusStock()).thenReturn(-1); 50 | dbDataInitializer.initDbData(); 51 | 52 | Item item = new Item(globalConfigs.itemPrefix() + 9, 5); 53 | Item item1 = new Item(globalConfigs.itemPrefix() + 5, 19); 54 | Item item2 = new Item(globalConfigs.itemPrefix() + 2, 8); 55 | Order order = new Order( 56 | "orderNumber", List.of(item, item1, item2), 3L); 57 | 58 | Order orderResult = orderManagementService.processStockRequest(order); 59 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.FAILED); 60 | } 61 | 62 | @Test 63 | void processStockRequest_newOrder_itemNotFound() { 64 | Mockito.when(globalConfigs.statusStock()).thenReturn(1); 65 | dbDataInitializer.initDbData(); 66 | 67 | Item item = new Item(globalConfigs.itemPrefix() + 9, 5); 68 | Item item1 = new Item("notFound", 3); 69 | Item item2 = new Item(globalConfigs.itemPrefix() + 2, 8); 70 | Order order = new Order( 71 | "orderNumber", List.of(item, item1, item2), 3L); 72 | 73 | Order orderResult = orderManagementService.processStockRequest(order); 74 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.FAILED 75 | ); 76 | } 77 | 78 | @Test 79 | void processStockRequest_rollback_itemQuantityIncreased() { 80 | Mockito.when(globalConfigs.statusStock()).thenReturn(1); 81 | String itemId = globalConfigs.itemPrefix() + 1; 82 | dbDataInitializer.initDbData(); 83 | 84 | Item item = new Item(itemId, 11); 85 | Order order = new Order( 86 | "orderNumber", List.of(item), 3L); 87 | order.setStockStatus(OrderStatus.ROLLBACK); 88 | orderManagementService.processStockRequest(order); 89 | 90 | StockItem itemResult = stockService.findByItemNumbers(List.of(itemId),true).get(0); 91 | assertThat(itemResult.getQuantity()).isEqualTo(100010); 92 | } 93 | } -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/service/OrderManagementService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.service; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import com.sylleryum.order.exception.OrderException; 7 | import com.sylleryum.order.producer.KafkaProducer; 8 | import com.sylleryum.order.util.OrderConverter; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @Service 14 | @Slf4j 15 | public class OrderManagementService { 16 | 17 | private final OrderService orderService; 18 | private final OrderConverter orderConverter; 19 | private final KafkaProducer kafkaProducer; 20 | 21 | public OrderManagementService(OrderService orderService, OrderConverter orderConverter, KafkaProducer kafkaProducer) { 22 | this.orderService = orderService; 23 | this.orderConverter = orderConverter; 24 | this.kafkaProducer = kafkaProducer; 25 | } 26 | 27 | @Transactional 28 | public Order processOrder(Order stockOrder, Order paymentOrder) { 29 | OrderDAO orderDAO = orderService.findByOrderNumber(stockOrder.getOrderNumber()).orElseThrow(() -> { 30 | log.error("something is wrong, order {} should be available but isn't, " + 31 | "if here, put a breakpoint to check it",stockOrder.getOrderNumber()); 32 | return new OrderException("order not found"); 33 | }); 34 | orderDAO.setStockStatus(stockOrder.getStockStatus()); 35 | orderDAO.setStockStatusReason(stockOrder.getStockStatusReason()); 36 | orderDAO.setPaymentStatus(paymentOrder.getPaymentStatus()); 37 | orderDAO.setPaymentStatusReason(paymentOrder.getPaymentStatusReason()); 38 | orderService.save(orderDAO); 39 | 40 | //where notifications can be triggered to user informing the final status of the order 41 | kafkaProducer.sendNotification(orderDAO.getOrderNumber(), orderConverter.orderDaoToKafka(orderDAO)); 42 | 43 | if (orderDAO.getStockStatus().equalsIgnoreCase(OrderStatus.SUCCESS) && 44 | orderDAO.getPaymentStatus().equalsIgnoreCase(OrderStatus.SUCCESS)) { 45 | log.debug("order succeeded {}", orderDAO); 46 | return orderConverter.orderDaoToKafka(orderDAO); 47 | } 48 | 49 | if (orderDAO.getStockStatus().equalsIgnoreCase(OrderStatus.FAILED) && 50 | orderDAO.getPaymentStatus().equalsIgnoreCase(OrderStatus.FAILED)){ 51 | log.debug("order failed {}", orderDAO); 52 | return orderConverter.orderDaoToKafka(orderDAO); 53 | } 54 | 55 | //if arrived here, at least one service failed 56 | if (orderDAO.getStockStatus().equalsIgnoreCase(OrderStatus.FAILED)) { 57 | //both failed 58 | if (orderDAO.getPaymentStatus().equalsIgnoreCase(OrderStatus.FAILED)) { 59 | log.debug("rollback order, both failed {}", orderDAO); 60 | orderDAO.setStockStatus(OrderStatus.ROLLBACK); 61 | orderDAO.setPaymentStatus(OrderStatus.ROLLBACK); 62 | return orderConverter.orderDaoToKafka(orderDAO); 63 | } 64 | //only stockOrder failed 65 | log.debug("rollback order, only stockOrder failed {}", orderDAO); 66 | orderDAO.setPaymentStatus(OrderStatus.ROLLBACK); 67 | return orderConverter.orderDaoToKafka(orderDAO); 68 | } 69 | 70 | //if arrived here, only payment failed 71 | log.debug("rollback order, only payment failed {}", orderDAO); 72 | orderDAO.setStockStatus(OrderStatus.ROLLBACK); 73 | return orderConverter.orderDaoToKafka(orderDAO); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /order/src/main/java/com/sylleryum/order/util/OrderGenerator.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.util; 2 | 3 | import com.sylleryum.order.entity.ItemDAO; 4 | import com.sylleryum.order.entity.OrderDAO; 5 | import com.sylleryum.common.config.GlobalConfigs; 6 | import com.sylleryum.order.exception.OrderException; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.UUID; 14 | 15 | @Component 16 | public class OrderGenerator { 17 | 18 | private final GlobalConfigs globalConfigs; 19 | private final long maxItemsAllowed; 20 | private long currentItemNumber=1; 21 | 22 | public OrderGenerator(GlobalConfigs globalConfigs, 23 | @Value("${max.items.allowed}") long maxItemsAllowed) { 24 | this.globalConfigs = globalConfigs; 25 | this.maxItemsAllowed = maxItemsAllowed; 26 | } 27 | 28 | /** 29 | * Generate new orderDAOs (to be persisted), quantity of item inside each order is determined by {@link GlobalConfigs} 30 | * @param amountOrders how many orders. Default = 5 31 | * @param amountItems how many different items inside each order (for each different item the quantity is determined by {@link GlobalConfigs}. Default = 4 32 | * @return List of {@link OrderDAO} ready to be persisted 33 | */ 34 | public List generateNewOrderDAOs(Optional amountOrders, Optional amountItems) throws OrderException{ 35 | long totalItemAmount = amountOrders.orElse(1L) * amountItems.orElse(1L); 36 | if (totalItemAmount > maxItemsAllowed){ 37 | throw new OrderException("amount of total items exceed the amount allowed. " + 38 | "Total amount passed: "+totalItemAmount+ " max amount allowed : "+maxItemsAllowed); 39 | } 40 | List orderDAOList = new ArrayList<>(); 41 | 42 | for (int i = 0; i < amountOrders.orElse(5L); i++) { 43 | OrderDAO orderDAO = new OrderDAO(UUID.randomUUID().toString(), 44 | null, 45 | Math.floor(Math.random() * (1000 - 1 + 1) + 1)); 46 | 47 | orderDAO.setItems(generateNewItemDAOs(amountItems.orElse(4L), orderDAO)); 48 | orderDAOList.add(orderDAO); 49 | } 50 | 51 | return orderDAOList; 52 | } 53 | 54 | /** 55 | * generates list of {@link ItemDAO} to be included inside an {@link OrderDAO} 56 | * @param amountItems how many items to be generated 57 | * @param orderDAO the order to be linked with this list 58 | * @return 59 | */ 60 | private List generateNewItemDAOs (Long amountItems, OrderDAO orderDAO) { 61 | List itemDaoList = new ArrayList<>(); 62 | 63 | for (int j = 0; j < amountItems; j++) { 64 | itemDaoList.add(ItemDAO.builder() 65 | .itemNumber(globalConfigs.itemPrefix()+currentItemNumber) 66 | .quantity(generateQuantity()) 67 | .order(orderDAO) 68 | .build()); 69 | currentItemNumber++; 70 | } 71 | 72 | return itemDaoList; 73 | } 74 | 75 | /** 76 | * used to determine the quantity of different items inside an order based on {@link GlobalConfigs} (e.g. all items should have success to reserve?). 77 | * all items in stock microservice have 10 units, therefore quantities above this will fail. 78 | * @return number representing the quantity of an item inside an order (e.g. an order of 16 microwaves) 79 | */ 80 | private int generateQuantity() { 81 | if (globalConfigs.statusStock() == globalConfigs.SUCCESS) { 82 | return (int) Math.floor(Math.random() * (10 - 1 + 1) + 1); 83 | } else if (globalConfigs.statusStock() == globalConfigs.FAILURE) { 84 | return (int) Math.floor(Math.random() * (20 - 11 + 1) + 11); 85 | } 86 | //if config is invalid, return success, 87 | // could be configured differently (e.g. throw exception for no configuration found) 88 | return (int) Math.floor(Math.random() * (10 - 1 + 1) + 1); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /order/src/test/java/com/sylleryum/order/service/OrderManagementServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.order.service; 2 | 3 | import com.sylleryum.common.entity.Order; 4 | import com.sylleryum.common.util.OrderStatus; 5 | import com.sylleryum.order.entity.OrderDAO; 6 | import com.sylleryum.order.producer.KafkaProducer; 7 | import com.sylleryum.order.util.OrderConverter; 8 | import com.sylleryum.order.util.OrderGenerator; 9 | import org.junit.jupiter.api.Test; 10 | import org.mockito.Mockito; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | 15 | import java.util.Optional; 16 | 17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 18 | 19 | @SpringBootTest 20 | class OrderManagementServiceTest { 21 | 22 | @MockBean 23 | OrderService orderService; 24 | @MockBean 25 | KafkaProducer kafkaProducer; 26 | @Autowired 27 | OrderConverter orderConverter; 28 | @Autowired 29 | OrderGenerator orderGenerator; 30 | @Autowired 31 | OrderManagementService orderManagementService; 32 | 33 | 34 | @Test 35 | void processOrder_allSuccess_true() { 36 | OrderDAO orderDAO = orderGenerator.generateNewOrderDAOs(Optional.of(1L), Optional.of(1L)).get(0); 37 | orderDAO.setStockStatus(OrderStatus.SUCCESS); 38 | orderDAO.setPaymentStatus(OrderStatus.SUCCESS); 39 | Order order = orderConverter.orderDaoToKafka(orderDAO); 40 | 41 | Mockito.when(orderService.save(orderDAO)).thenReturn(orderDAO); 42 | Mockito.when(orderService.findByOrderNumber(orderDAO.getOrderNumber())).thenReturn(Optional.of(orderDAO)); 43 | Order orderResult = orderManagementService.processOrder(order, order); 44 | 45 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.SUCCESS); 46 | assertThat(orderResult.getPaymentStatus()).isEqualTo(OrderStatus.SUCCESS); 47 | } 48 | 49 | @Test 50 | void processOrder_allFailed_allFailed() { 51 | OrderDAO orderDAO = orderGenerator.generateNewOrderDAOs(Optional.of(1L), Optional.of(1L)).get(0); 52 | orderDAO.setStockStatus(OrderStatus.FAILED); 53 | orderDAO.setPaymentStatus(OrderStatus.FAILED); 54 | Order order = orderConverter.orderDaoToKafka(orderDAO); 55 | 56 | Mockito.when(orderService.save(orderDAO)).thenReturn(orderDAO); 57 | Mockito.when(orderService.findByOrderNumber(orderDAO.getOrderNumber())).thenReturn(Optional.of(orderDAO)); 58 | Order orderResult = orderManagementService.processOrder(order, order); 59 | 60 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.FAILED); 61 | assertThat(orderResult.getPaymentStatus()).isEqualTo(OrderStatus.FAILED); 62 | } 63 | 64 | @Test 65 | void processOrder_stockFailed_paymentRollback() { 66 | OrderDAO orderDAO = orderGenerator.generateNewOrderDAOs(Optional.of(1L), Optional.of(1L)).get(0); 67 | orderDAO.setStockStatus(OrderStatus.FAILED); 68 | orderDAO.setPaymentStatus(OrderStatus.SUCCESS); 69 | Order order = orderConverter.orderDaoToKafka(orderDAO); 70 | 71 | Mockito.when(orderService.save(orderDAO)).thenReturn(orderDAO); 72 | Mockito.when(orderService.findByOrderNumber(orderDAO.getOrderNumber())).thenReturn(Optional.of(orderDAO)); 73 | Order orderResult = orderManagementService.processOrder(order, order); 74 | 75 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.FAILED); 76 | assertThat(orderResult.getPaymentStatus()).isEqualTo(OrderStatus.ROLLBACK); 77 | } 78 | 79 | @Test 80 | void processOrder_paymentFailed_stockRollback() { 81 | OrderDAO orderDAO = orderGenerator.generateNewOrderDAOs(Optional.of(1L), Optional.of(1L)).get(0); 82 | orderDAO.setStockStatus(OrderStatus.SUCCESS); 83 | orderDAO.setPaymentStatus(OrderStatus.FAILED); 84 | Order order = orderConverter.orderDaoToKafka(orderDAO); 85 | 86 | Mockito.when(orderService.save(orderDAO)).thenReturn(orderDAO); 87 | Mockito.when(orderService.findByOrderNumber(orderDAO.getOrderNumber())).thenReturn(Optional.of(orderDAO)); 88 | Order orderResult = orderManagementService.processOrder(order, order); 89 | 90 | assertThat(orderResult.getStockStatus()).isEqualTo(OrderStatus.ROLLBACK); 91 | assertThat(orderResult.getPaymentStatus()).isEqualTo(OrderStatus.FAILED); 92 | } 93 | } -------------------------------------------------------------------------------- /stock/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.4 9 | 10 | 11 | com.sylleryum 12 | stock 13 | 0.0.1-SNAPSHOT 14 | stock 15 | stock 16 | 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-mongodb 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-test 32 | test 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | 1.18.22 39 | provided 40 | 41 | 42 | 43 | org.springframework.kafka 44 | spring-kafka 45 | 46 | 47 | org.springframework.kafka 48 | spring-kafka-test 49 | test 50 | 51 | 52 | org.slf4j 53 | slf4j-log4j12 54 | 55 | 56 | junit 57 | junit 58 | test 59 | 60 | 61 | com.sylleryum 62 | common 63 | 0.0.2-SNAPSHOT 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | com/sylleryum/order/entity/** 73 | com/sylleryum/common/entity/** 74 | 75 | 76 | org.jacoco 77 | jacoco-maven-plugin 78 | 0.8.5 79 | 80 | 81 | 82 | default-prepare-agent 83 | 84 | prepare-agent 85 | 86 | 87 | 88 | 89 | 90 | report 91 | test 92 | 93 | report 94 | 95 | 96 | ../target 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | sylleryum/${project.artifactId} 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /payment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.6 9 | 10 | 11 | com.sylleryum 12 | payment 13 | 0.0.1-SNAPSHOT 14 | payment 15 | payment 16 | 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.kafka 26 | spring-kafka 27 | 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | 1.18.22 33 | provided 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.springframework.kafka 43 | spring-kafka-test 44 | test 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-data-jpa 50 | 51 | 52 | com.h2database 53 | h2 54 | runtime 55 | 56 | 57 | 58 | com.sylleryum 59 | common 60 | 0.0.2-SNAPSHOT 61 | 62 | 63 | junit 64 | junit 65 | test 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | com/sylleryum/order/entity/** 75 | com/sylleryum/common/entity/** 76 | 77 | 78 | org.jacoco 79 | jacoco-maven-plugin 80 | 0.8.5 81 | 82 | 83 | 84 | default-prepare-agent 85 | 86 | prepare-agent 87 | 88 | 89 | 90 | 91 | 92 | report 93 | test 94 | 95 | report 96 | 97 | 98 | ../target 99 | 100 | 101 | 102 | 103 | 104 | org.springframework.boot 105 | spring-boot-maven-plugin 106 | 107 | 108 | sylleryum/${project.artifactId} 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /order/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.4 9 | 10 | 11 | com.sylleryum 12 | order 13 | 0.0.1-SNAPSHOT 14 | order 15 | order 16 | 17 | 11 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | 27 | 28 | org.springframework.kafka 29 | spring-kafka 30 | 31 | 32 | org.springframework.kafka 33 | spring-kafka-test 34 | test 35 | 36 | 37 | 38 | org.testcontainers 39 | kafka 40 | 1.16.3 41 | test 42 | 43 | 44 | org.apache.kafka 45 | kafka-streams 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-data-jpa 58 | 59 | 60 | 61 | org.postgresql 62 | postgresql 63 | runtime 64 | 65 | 66 | 67 | org.projectlombok 68 | lombok 69 | 1.18.22 70 | provided 71 | 72 | 73 | 74 | com.sylleryum 75 | common 76 | 0.0.2-SNAPSHOT 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.jacoco 84 | jacoco-maven-plugin 85 | 0.8.5 86 | 87 | 88 | 89 | default-prepare-agent 90 | 91 | prepare-agent 92 | 93 | 94 | 95 | 96 | 97 | report 98 | test 99 | 100 | report 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-maven-plugin 111 | 112 | 113 | sylleryum/${project.artifactId} 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /resources/docker files/Run project on docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo: 5 | image: mongo 6 | environment: 7 | MONGO_INITDB_ROOT_USERNAME: root 8 | MONGO_INITDB_ROOT_PASSWORD: Mongo2022! 9 | ports: 10 | - "27017:27017" 11 | volumes: 12 | - mongodata:/data/db 13 | networks: 14 | - common 15 | 16 | mongo-express: 17 | image: mongo-express 18 | ports: 19 | - "8081:8081" 20 | environment: 21 | ME_CONFIG_MONGODB_URL: mongodb://root:Mongo2022!@mongo:27017/ 22 | ME_CONFIG_BASICAUTH_USERNAME: root 23 | ME_CONFIG_BASICAUTH_PASSWORD: Mongo2022! 24 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 25 | ME_CONFIG_MONGODB_ADMINPASSWORD: Mongo2022! 26 | ME_CONFIG_MONGODB_PORT: 27017 27 | links: 28 | - mongo 29 | depends_on: 30 | - mongo 31 | networks: 32 | - common 33 | 34 | PostgreSQL: 35 | image: postgres 36 | environment: 37 | POSTGRES_PASSWORD: "admin123" 38 | POSTGRES_DB: "kafka" 39 | healthcheck: 40 | test: ["CMD-SHELL", "pg_isready -U postgres"] 41 | interval: 10s 42 | timeout: 5s 43 | retries: 5 44 | volumes: 45 | - pgdata:/var/lib/postgresql/data:z 46 | networks: 47 | - common 48 | 49 | 50 | pgadmin-compose: 51 | image: dpage/pgadmin4 52 | environment: 53 | PGADMIN_DEFAULT_EMAIL: "test@test.com" 54 | PGADMIN_DEFAULT_PASSWORD: "PgAdmin123" 55 | ports: 56 | - "16543:80" 57 | depends_on: 58 | - PostgreSQL 59 | networks: 60 | - common 61 | 62 | zookeeper: 63 | image: confluentinc/cp-zookeeper:5.1.2 64 | restart: always 65 | environment: 66 | ZOOKEEPER_SERVER_ID: 1 67 | ZOOKEEPER_CLIENT_PORT: "2181" 68 | ZOOKEEPER_TICK_TIME: "2000" 69 | ZOOKEEPER_SERVERS: "zookeeper:22888:23888" 70 | ports: 71 | - "2181:2181" 72 | networks: 73 | - common 74 | 75 | kafka1: 76 | image: confluentinc/cp-kafka:7.0.0 77 | depends_on: 78 | - zookeeper 79 | ports: 80 | - "29092:29092" 81 | environment: 82 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 83 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 84 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 85 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092,PLAINTEXT_HOST://kafka1:29092 86 | KAFKA_ADVERTISED_HOST_NAME: kafka1 87 | KAFKA_BROKER_ID: 1 88 | KAFKA_BROKER_RACK: "r1" 89 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 90 | KAFKA_DELETE_TOPIC_ENABLE: "true" 91 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" 92 | KAFKA_SCHEMA_REGISTRY_URL: "schemaregistry:8085" 93 | KAFKA_JMX_PORT: 9991 94 | networks: 95 | - common 96 | 97 | schemaregistry: 98 | image: confluentinc/cp-schema-registry:5.1.2 99 | restart: always 100 | depends_on: 101 | - zookeeper 102 | environment: 103 | SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: "zookeeper:2181" 104 | SCHEMA_REGISTRY_HOST_NAME: schemaregistry 105 | SCHEMA_REGISTRY_LISTENERS: "http://0.0.0.0:8085" 106 | ports: 107 | - "8085:8085" 108 | networks: 109 | - common 110 | 111 | kafdrop: 112 | image: obsidiandynamics/kafdrop 113 | restart: "no" 114 | ports: 115 | - "9000:9000" 116 | environment: 117 | KAFKA_BROKERCONNECT: "kafka1:9092" 118 | depends_on: 119 | - kafka1 120 | networks: 121 | - common 122 | 123 | order: 124 | image: sylleryum/order 125 | ports: 126 | - "8080:8080" 127 | depends_on: 128 | PostgreSQL: 129 | condition: service_healthy 130 | networks: 131 | - common 132 | environment: 133 | - SPRING_DATASOURCE_URL=jdbc:postgresql://PostgreSQL:5432/postgres 134 | 135 | stock: 136 | image: sylleryum/stock 137 | ports: 138 | - "8082:8082" 139 | depends_on: 140 | - mongo 141 | - kafka1 142 | - kafdrop 143 | - schemaregistry 144 | - zookeeper 145 | - order 146 | networks: 147 | - common 148 | 149 | payment: 150 | image: sylleryum/payment 151 | ports: 152 | - "8083:8083" 153 | depends_on: 154 | - PostgreSQL 155 | - kafka1 156 | - kafdrop 157 | - schemaregistry 158 | - zookeeper 159 | - order 160 | networks: 161 | - common 162 | 163 | notification: 164 | image: sylleryum/notification 165 | ports: 166 | - "8084:8084" 167 | depends_on: 168 | - PostgreSQL 169 | - kafka1 170 | - kafdrop 171 | - schemaregistry 172 | - zookeeper 173 | - order 174 | networks: 175 | - common 176 | 177 | networks: 178 | common: 179 | driver: bridge 180 | 181 | volumes: 182 | mongodata: 183 | pgdata: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![codecov](https://codecov.io/gh/sylleryum/kafka-microservices-with-saga/branch/main/graph/badge.svg?token=66D2JP4X2K)](https://codecov.io/gh/sylleryum/kafka-microservices-with-saga) 2 | ![example workflow](https://github.com/sylleryum/kafka-microservices-with-saga/actions/workflows/workflow.yml/badge.svg) 3 | 4 | # Microservices with Kafka and Saga pattern using Spring boot 5 | This project simulates a system for processing orders (i.e., a purchase) of items (E.g.: an order of a fridge and a camera) which is constituted of 4 microservices: order, stock, payment and notification service. 6 | ## High level architecture: 7 |

8 | 9 |

10 | 11 | **Note:** as the main objective of this project is to demonstrate Kafka, majority of microservice patterns are ignored as well as some best practices for simplicity/readability’s sake (E.g.: Transactional outbox and coding to the interface). 12 | 13 | ## Getting started / Installation: 14 | 15 | ### Option 1: Running locally 16 | - Clone this repo. 17 | - Run the docker compose file inside “resources/docker files/Run project locally” directory (docker-compose up), if mongo-express fails to initialize, simply re-run it. 18 | - Run all the microservices (any order of initialization is fine). 19 | 20 | ### Option 2: Running on Docker 21 | - Simply run the docker compose file inside “resources/docker files/Run project on docker” directory (docker-compose up), if mongo-express fails to initialize, simply re-run it. 22 | 23 | ## Instructions: 24 | 25 | - Send orders through order service’s endpoint /api/v1/order specifying the amount of orders to send and the amount of items inside each order through query param o (order) and i (item). 26 | - E.g.: localhost:8080/api/v1/order?o=2&i=3 will send 2 orders, each order contains 3 items within itself. 27 | - You can easily check the final result of each order through notification service’s console or check each topic through kafkdrop (localhost:9000/). 28 | - You can change the expected order result (success/failure) and other configurations through shared.properties inside common module. 29 | 30 | ## How it works: 31 | Once a new order is received, the order service does the initial processing and sends a new event to kafka: 32 |

33 | 34 |

35 | All microservices involved in the order will perform their corresponding operations and send a confirmation back to Kafka (success/failure): 36 |

37 | 38 |

39 | Order service then uses Kafka Streams to join all the confirmations received (inner join). If all services returned a success event, order has been fully processed (order completed). If any service returns a failure message, order service then triggers an event of rollback which will be processed by all other services. 40 | Order service also sends the final order status to Kafka, notification service simulates then a notification message to user informing the final status of his/her order: 41 |

42 | 43 |

44 | 45 | ## Configurations: 46 | - If running locally, configurations of the microservices can be changed through shared.application located at common\src\resources\ (explanaition of relevant configurations are included in this file). 47 | - 48 | ## Considerations regarding this project and best practices: 49 | - Kafka may be tricky to handle proficiently duplications/idempotency. In this project the approach of [enabling idempotent producer was used](https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_enable.idempotence). Consumer and its commit offset strategy should be considered also. E.g.: [Idempotent Kafka Consumer](https://medium.com/techwasti/idempotent-kafka-consumer-442f9aec991e) 50 | - This projects uses 2 DBs, Postgres for Order service and MongoDB for Stock service, this is only to showcase microservices and Kafka with different DBs. 51 | - Common module should be replaced in a real scenario for a better approach as externalized configuration pattern. 52 | - For simplicity sake, Kafka producer/consumer are using a shared entity located in the common module, in a real scenario Avro/schema registry (included in the docker-compose file) is advised. 53 | - A caveat of using Kafka Streams and inner join to process the results of an order being processed by order services is that it is time windowed, if for any reason a service takes longer than the window time to answer an order, the orchestrator will never process the confirmation of the order. A few alternatives is to use individual listeners in the orchestrator or outer join and schedule a task to verify the order after join window has closed. 54 | - If a rollback is needed, orchestrator (order service) will send an event (order) that is consumed by all services involved in processing an order rather than individual rollback events (E.g.: rollback event to payment service) as usually maximum 1 service will fail and because of this, all other services will have to rollback and therefore, consume an event. -------------------------------------------------------------------------------- /stock/src/main/java/com/sylleryum/stock/service/OrderManagementService.java: -------------------------------------------------------------------------------- 1 | package com.sylleryum.stock.service; 2 | 3 | import com.sylleryum.common.entity.Item; 4 | import com.sylleryum.common.entity.Order; 5 | import com.sylleryum.common.util.OrderStatus; 6 | import com.sylleryum.stock.entity.StockItem; 7 | import com.sylleryum.stock.exceptions.StockException; 8 | import com.sylleryum.stock.producer.KafkaProducer; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.kafka.annotation.EnableKafka; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Comparator; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | @Service 19 | @EnableKafka 20 | @Slf4j 21 | public class OrderManagementService { 22 | 23 | private final StockService stockService; 24 | private final KafkaProducer kafkaProducer; 25 | 26 | public OrderManagementService(StockService stockService, KafkaProducer kafkaProducer) { 27 | this.stockService = stockService; 28 | this.kafkaProducer = kafkaProducer; 29 | } 30 | 31 | public Order processStockRequest(Order order) { 32 | if (order.getStockStatus().equalsIgnoreCase(OrderStatus.NEW)) { 33 | return newOrder(order); 34 | } else if (order.getStockStatus().equalsIgnoreCase(OrderStatus.ROLLBACK)) { 35 | return rollbackOrder(order); 36 | } 37 | return order; 38 | 39 | } 40 | 41 | /** 42 | * processes rollback 43 | * 44 | * @param order 45 | * @return 46 | */ 47 | private Order rollbackOrder(Order order) { 48 | log.debug("rollback stock request {} ", order.getOrderNumber()); 49 | List rollbackStockItems = order.getItems(); 50 | List currentStockItems = stockService.findByItemNumbers( 51 | rollbackStockItems.stream().map(Item::getItemNumber).collect(Collectors.toList()), 52 | true); 53 | List stockItemsUpdated = rollbackStock(rollbackStockItems, currentStockItems); 54 | if (currentStockItems.isEmpty()) { 55 | log.error("one of the items wasn't found on stock rollback{}", order); 56 | throw new StockException("one of the items wasn't found on stock rollback" + order); 57 | } 58 | 59 | return order; 60 | } 61 | 62 | /** 63 | * processes new orders 64 | * 65 | * @param order 66 | * @return 67 | */ 68 | private Order newOrder(Order order) { 69 | log.debug("new stock request {} ", order.getOrderNumber()); 70 | List newStockItems = new ArrayList<>(order.getItems()); 71 | 72 | //check if all the items received are truly present in the DB 73 | List currentStockItems = stockService.findByItemNumbers( 74 | newStockItems.stream().map(Item::getItemNumber).collect(Collectors.toList()), 75 | true); 76 | if (!currentStockItems.isEmpty()) { 77 | //if all items received are present, 78 | //check if quantity of received stock items is bigger then current stock 79 | List stockItemsUpdated = reserveStock(newStockItems, currentStockItems); 80 | if (stockItemsUpdated.isEmpty()) { 81 | log.debug("stock reserve failed for order {} " + 82 | "(quantity requested for one of the items exceeds stock)", order); 83 | order.setStockStatus(OrderStatus.FAILED); 84 | order.setStockStatusReason("Quantity for one of the items ordered exceed quantity in stock"); 85 | } else { 86 | stockService.saveAll(stockItemsUpdated); 87 | order.setStockStatus(OrderStatus.SUCCESS); 88 | } 89 | } else { 90 | order.setStockStatus(OrderStatus.FAILED); 91 | log.debug("one of the items wasn't found on stock {}", order); 92 | order.setStockStatusReason("One of the items wasn't found on stock"); 93 | } 94 | kafkaProducer.send(order.getOrderNumber(), order); 95 | log.debug("new order result {} ", order); 96 | return order; 97 | } 98 | 99 | /** 100 | * increase or decrease the quantity of stock item 101 | * 102 | * @param itemsReceived items for new order or rollback 103 | * @param currentStockItems respective items in stock matching itemsReceived 104 | * @param newOrder whether it's a new order or a rollback 105 | * @return list of items with quantity updated 106 | */ 107 | private List changeStockQuantity(List itemsReceived, List currentStockItems, boolean newOrder) { 108 | itemsReceived = new ArrayList<>(itemsReceived); 109 | itemsReceived.sort(Comparator.comparing(Item::getItemNumber)); 110 | currentStockItems.sort(Comparator.comparing(StockItem::getItemNumber)); 111 | List stockUpdated = List.copyOf(currentStockItems); 112 | 113 | for (int i = 0; i < itemsReceived.size(); i++) { 114 | //decrease quantity for new order 115 | if (newOrder) { 116 | if (itemsReceived.get(i).getQuantity() > currentStockItems.get(i).getQuantity()) { 117 | return List.of(); 118 | } 119 | stockUpdated.get(i).setQuantity(currentStockItems.get(i).getQuantity() - itemsReceived.get(i).getQuantity()); 120 | //increase quantity for rollback 121 | } else { 122 | stockUpdated.get(i).setQuantity(currentStockItems.get(i).getQuantity() + itemsReceived.get(i).getQuantity()); 123 | } 124 | } 125 | return stockUpdated; 126 | } 127 | 128 | /** 129 | * deduce quantity of stockItems 130 | * 131 | * @param itemsReceived items for new order 132 | * @param currentStockItems items in stock matching itemsReceived 133 | * @return items received after persisted 134 | */ 135 | private List reserveStock(List itemsReceived, List currentStockItems) { 136 | return changeStockQuantity(itemsReceived, currentStockItems, true); 137 | } 138 | 139 | /** 140 | * increase quantity of stockItems 141 | * 142 | * @param itemsReceived items for rollback 143 | * @param currentStockItems items in stock matching itemsReceived 144 | * @return items received after persisted 145 | */ 146 | private List rollbackStock(List itemsReceived, List currentStockItems) { 147 | List stockItemsUpdated = changeStockQuantity(itemsReceived, currentStockItems, false); 148 | return stockService.saveAll(stockItemsUpdated); 149 | } 150 | 151 | } -------------------------------------------------------------------------------- /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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /order/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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /stock/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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /common/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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /payment/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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /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 | # Maven 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 /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /common/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 | # Maven 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 /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /order/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 | # Maven 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 /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | --------------------------------------------------------------------------------