From 28cdc0ddc0f259ae00b1657b9251a0f029d33087 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 27 May 2022 23:55:58 -0300 Subject: [PATCH 01/42] BAEL-5370 - MongoDB Composite Key First Draft. --- .../SpringBootCompositeKeyApplication.java | 15 ++ .../composite/key/dao/CustomerRepository.java | 12 ++ .../composite/key/dao/SaleRepository.java | 12 ++ .../composite/key/dao/TicketRepository.java | 10 ++ .../boot/composite/key/data/Customer.java | 49 ++++++ .../boot/composite/key/data/Sale.java | 38 +++++ .../boot/composite/key/data/Ticket.java | 31 ++++ .../boot/composite/key/data/TicketId.java | 56 +++++++ .../key/service/CustomerService.java | 66 +++++++++ .../composite/key/web/CustomerController.java | 88 +++++++++++ .../boot.composite.key/app.properties | 1 + .../key/CustomerServiceIntegrationTest.java | 140 ++++++++++++++++++ 12 files changed, 518 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java new file mode 100644 index 0000000000..e9f8eab1dd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.boot.composite.key; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@SpringBootApplication +@PropertySource("classpath:boot.composite.key/app.properties") +@EnableMongoRepositories(basePackages = { "com.baeldung.boot.composite.key" }) +public class SpringBootCompositeKeyApplication { + public static void main(String... args) { + SpringApplication.run(SpringBootCompositeKeyApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java new file mode 100644 index 0000000000..6953a21ddd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.composite.key.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Customer; + +public interface CustomerRepository extends MongoRepository { + + Optional findByStoreIdAndNumber(Long storeId, Long number); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java new file mode 100644 index 0000000000..3caa33d465 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.composite.key.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.TicketId; + +public interface SaleRepository extends MongoRepository { + Optional findByTicketId(TicketId ticketId); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java new file mode 100644 index 0000000000..b02ea461d2 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.boot.composite.key.dao; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; + +public interface TicketRepository extends MongoRepository { + +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java new file mode 100644 index 0000000000..d4bb1ef40c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java @@ -0,0 +1,49 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(name = "customer_idx", def = "{ 'storeId': 1, 'number': 1 }", unique = true) +public class Customer { + @Id + private String id; + + private Long storeId; + private Long number; + + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public Long getNumber() { + return number; + } + + public void setNumber(Long number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java new file mode 100644 index 0000000000..a81c91674c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java @@ -0,0 +1,38 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(name = "sale_idx", def = "{ 'ticketId': 1 }", unique = true) +public class Sale { + @Id + private String id; + + private TicketId ticketId; + private Double value; + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + + public TicketId getTicketId() { + return ticketId; + } + + public void setTicketId(TicketId ticketId) { + this.ticketId = ticketId; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java new file mode 100644 index 0000000000..7a8ebe1ab3 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java @@ -0,0 +1,31 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Ticket { + @Id + private TicketId id; + + private String event; + + public Ticket() { + } + + public TicketId getId() { + return id; + } + + public void setId(TicketId id) { + this.id = id; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java new file mode 100644 index 0000000000..76fbf81391 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java @@ -0,0 +1,56 @@ +package com.baeldung.boot.composite.key.data; + +public class TicketId { + private String venue; + private String date; + + public TicketId() { + } + + public String getVenue() { + return venue; + } + + public void setVenue(String venue) { + this.venue = venue; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((date == null) ? 0 : date.hashCode()); + result = prime * result + ((venue == null) ? 0 : venue.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TicketId other = (TicketId) obj; + if (date == null) { + if (other.date != null) + return false; + } else if (!date.equals(other.date)) + return false; + if (venue == null) { + if (other.venue != null) + return false; + } else if (!venue.equals(other.venue)) + return false; + return true; + } +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java new file mode 100644 index 0000000000..476209bce9 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java @@ -0,0 +1,66 @@ +package com.baeldung.boot.composite.key.service; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.baeldung.boot.composite.key.dao.CustomerRepository; +import com.baeldung.boot.composite.key.dao.SaleRepository; +import com.baeldung.boot.composite.key.dao.TicketRepository; +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; + +@Service +public class CustomerService { + @Autowired + private CustomerRepository customerRepository; + + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private SaleRepository saleRepository; + + public Optional find(TicketId id) { + return ticketRepository.findById(id); + } + + public Ticket insert(Ticket ticket) { + return ticketRepository.insert(ticket); + } + + public Ticket save(Ticket ticket) { + return ticketRepository.save(ticket); + } + + public Optional findCustomerById(String id) { + return customerRepository.findById(id); + } + + public Optional findCustomerByIndex(Long storeId, Long number) { + return customerRepository.findByStoreIdAndNumber(storeId, number); + } + + public Customer insert(Customer customer) { + return customerRepository.insert(customer); + } + + public Customer save(Customer customer) { + return customerRepository.save(customer); + } + + public Sale insert(Sale sale) { + return saleRepository.insert(sale); + } + + public Optional findSaleByTicketId(TicketId ticketId) { + return saleRepository.findByTicketId(ticketId); + } + + public Optional findSaleById(String id) { + return saleRepository.findById(id); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java new file mode 100644 index 0000000000..83afe0ae42 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java @@ -0,0 +1,88 @@ +package com.baeldung.boot.composite.key.web; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; +import com.baeldung.boot.composite.key.service.CustomerService; + +@RestController +@RequestMapping("/customer") +public class CustomerController { + @Autowired + private CustomerService customerService; + + // @Autowired + // private TicketRepository ticketRepository; + // + // @GetMapping("/ticket") + // public Optional getTicket(TicketId id) { + // return ticketRepository.findById(id); + // } + // + // @PostMapping("/ticket") + // public Ticket postTicket(@RequestBody Ticket ticket) { + // return ticketRepository.insert(ticket); + // } + + @GetMapping("/ticket") + public Optional getTicket(TicketId id) { + return customerService.find(id); + } + + @PostMapping("/ticket") + public Ticket postTicket(@RequestBody Ticket ticket) { + return customerService.insert(ticket); + } + + @PutMapping("/ticket") + public Ticket putTicket(@RequestBody Ticket ticket) { + return customerService.save(ticket); + } + + @GetMapping("/{id}") + public Optional getCustomer(@PathVariable String id) { + return customerService.findCustomerById(id); + } + + @GetMapping("/{storeId}/{number}") + public Optional getCustomerByIndex(@PathVariable Long storeId, @PathVariable Long number) { + return customerService.findCustomerByIndex(storeId, number); + } + + @PostMapping + public Customer postCustomer(@RequestBody Customer customer) { + return customerService.insert(customer); + } + + @PutMapping + public Customer putCustomer(@RequestBody Customer customer) { + return customerService.save(customer); + } + + @PostMapping("/sale") + public Sale postSale(@RequestBody Sale sale) { + return customerService.insert(sale); + } + + @GetMapping("/sale/{id}") + public Optional getSale(@PathVariable String id) { + return customerService.findSaleById(id); + } + + @GetMapping("/sale") + public Optional getSale(TicketId ticketId) { + return customerService.findSaleByTicketId(ticketId); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties new file mode 100644 index 0000000000..a73a94d850 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties @@ -0,0 +1 @@ +spring.data.mongodb.auto-index-creation=true diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java new file mode 100644 index 0000000000..4f779ebf02 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java @@ -0,0 +1,140 @@ +package com.baeldung.boot.composite.key; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; +import com.baeldung.boot.composite.key.service.CustomerService; + +@SpringBootTest +@DirtiesContext +@RunWith(SpringRunner.class) +public class CustomerServiceIntegrationTest { + @Autowired + private CustomerService service; + + private static Ticket ticket; + private static TicketId ticketId; + + @BeforeClass + public static void setup() { + ticket = new Ticket(); + ticket.setEvent("Event A"); + + ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue A"); + ticket.setId(ticketId); + } + + @Test + public void givenCompositeId_whenObjectSaved_thenIdMatches() { + Ticket savedTicket = service.insert(ticket); + assertEquals(savedTicket.getId(), ticket.getId()); + } + + @Test + public void givenCompositeId_whenSearchingByIdObject_thenFound() { + Optional optionalTicket = service.find(ticketId); + + assertThat(optionalTicket.isPresent()); + Ticket savedTicket = optionalTicket.get(); + + assertEquals(savedTicket.getId(), ticketId); + } + + @Test + public void givenCompoundUniqueIndex_whenSearchingByGeneratedId_thenFound() { + Customer customer = new Customer(); + customer.setName("Name"); + customer.setNumber(0l); + customer.setStoreId(0l); + + Customer savedCustomer = service.insert(customer); + + Optional optional = service.findCustomerById(savedCustomer.getId()); + + assertThat(optional.isPresent()); + } + + @Test + public void givenCompositeId_whenDupeInsert_thenExceptionIsThrown() { + Ticket ticket = new Ticket(); + ticket.setEvent("C"); + + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("V"); + ticket.setId(ticketId); + + assertThrows(DuplicateKeyException.class, () -> { + service.insert(ticket); + service.insert(ticket); + }); + } + + @Test + public void givenCompositeId_whenDupeSave_thenObjectUpdated() { + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue"); + + Ticket ticketA = new Ticket(); + ticketA.setEvent("A"); + ticketA.setId(ticketId); + + service.save(ticketA); + + Ticket ticketB = new Ticket(); + ticketB.setEvent("B"); + ticketB.setId(ticketId); + + Ticket savedTicket = service.save(ticketB); + assertEquals(savedTicket.getEvent(), ticketB.getEvent()); + } + + @Test + public void givenCompoundUniqueIndex_whenDupeInsert_thenExceptionIsThrown() { + Customer customer = new Customer(); + customer.setName("Name"); + customer.setNumber(1l); + customer.setStoreId(2l); + + assertThrows(DuplicateKeyException.class, () -> { + service.insert(customer); + service.insert(customer); + }); + } + + @Test + public void givenCompoundUniqueIndex_whenDupeSave_thenExceptionIsThrown() { + Customer customerA = new Customer(); + customerA.setName("Name A"); + customerA.setNumber(1l); + customerA.setStoreId(2l); + + Customer customerB = new Customer(); + customerB.setName("Name B"); + customerB.setNumber(1l); + customerB.setStoreId(2l); + + assertThrows(DuplicateKeyException.class, () -> { + service.save(customerA); + service.save(customerB); + }); + } +} From 0260f6373e8e148ab32a5ac93239b4258f092ce7 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 27 May 2022 23:57:12 -0300 Subject: [PATCH 02/42] removing comments --- .../boot/composite/key/web/CustomerController.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java index 83afe0ae42..9e41d13e14 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java @@ -23,19 +23,6 @@ public class CustomerController { @Autowired private CustomerService customerService; - // @Autowired - // private TicketRepository ticketRepository; - // - // @GetMapping("/ticket") - // public Optional getTicket(TicketId id) { - // return ticketRepository.findById(id); - // } - // - // @PostMapping("/ticket") - // public Ticket postTicket(@RequestBody Ticket ticket) { - // return ticketRepository.insert(ticket); - // } - @GetMapping("/ticket") public Optional getTicket(TicketId id) { return customerService.find(id); From 8a75af488ba08fa1e7a4e2fc663ac3d7309c7f3a Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 3 Jun 2022 10:06:51 -0300 Subject: [PATCH 03/42] BAEL-5370 Test could fail if ran in a different order: givenCompositeId_whenSearchingByIdObject_thenFound --- .../boot/composite/key/data/Ticket.java | 6 +++ .../key/CustomerServiceIntegrationTest.java | 44 +++++++------------ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java index 7a8ebe1ab3..d77b54c513 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java @@ -13,6 +13,12 @@ public class Ticket { public Ticket() { } + public Ticket(TicketId id, String event) { + super(); + this.id = id; + this.event = event; + } + public TicketId getId() { return id; } diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java index 4f779ebf02..658be343f1 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertThrows; import java.util.Optional; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,28 +26,26 @@ public class CustomerServiceIntegrationTest { @Autowired private CustomerService service; - private static Ticket ticket; - private static TicketId ticketId; - - @BeforeClass - public static void setup() { - ticket = new Ticket(); - ticket.setEvent("Event A"); - - ticketId = new TicketId(); - ticketId.setDate("2020-01-01"); - ticketId.setVenue("Venue A"); - ticket.setId(ticketId); - } - @Test public void givenCompositeId_whenObjectSaved_thenIdMatches() { + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue A"); + + Ticket ticket = new Ticket(ticketId, "Event A"); Ticket savedTicket = service.insert(ticket); + assertEquals(savedTicket.getId(), ticket.getId()); } @Test public void givenCompositeId_whenSearchingByIdObject_thenFound() { + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue B"); + + service.insert(new Ticket(ticketId, "Event B")); + Optional optionalTicket = service.find(ticketId); assertThat(optionalTicket.isPresent()); @@ -73,13 +70,11 @@ public class CustomerServiceIntegrationTest { @Test public void givenCompositeId_whenDupeInsert_thenExceptionIsThrown() { - Ticket ticket = new Ticket(); - ticket.setEvent("C"); - TicketId ticketId = new TicketId(); ticketId.setDate("2020-01-01"); ticketId.setVenue("V"); - ticket.setId(ticketId); + + Ticket ticket = new Ticket(ticketId, "Event C"); assertThrows(DuplicateKeyException.class, () -> { service.insert(ticket); @@ -93,17 +88,12 @@ public class CustomerServiceIntegrationTest { ticketId.setDate("2020-01-01"); ticketId.setVenue("Venue"); - Ticket ticketA = new Ticket(); - ticketA.setEvent("A"); - ticketA.setId(ticketId); - + Ticket ticketA = new Ticket(ticketId, "A"); service.save(ticketA); - Ticket ticketB = new Ticket(); - ticketB.setEvent("B"); - ticketB.setId(ticketId); - + Ticket ticketB = new Ticket(ticketId, "B"); Ticket savedTicket = service.save(ticketB); + assertEquals(savedTicket.getEvent(), ticketB.getEvent()); } From 793ada0100c3974cba0eca0e3674cdb36de51ace Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Mon, 13 Jun 2022 23:37:28 -0300 Subject: [PATCH 04/42] BAEL-5370 removing compound index related stuff --- .../SpringBootCompositeKeyApplication.java | 2 - .../composite/key/dao/CustomerRepository.java | 12 ----- .../composite/key/dao/SaleRepository.java | 12 ----- .../boot/composite/key/data/Customer.java | 49 ------------------- .../boot/composite/key/data/Sale.java | 38 -------------- .../key/service/CustomerService.java | 38 -------------- .../composite/key/web/CustomerController.java | 38 -------------- .../boot.composite.key/app.properties | 1 - .../key/CustomerServiceIntegrationTest.java | 46 ----------------- 9 files changed, 236 deletions(-) delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java index e9f8eab1dd..1322adbf77 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java @@ -2,11 +2,9 @@ package com.baeldung.boot.composite.key; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.PropertySource; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication -@PropertySource("classpath:boot.composite.key/app.properties") @EnableMongoRepositories(basePackages = { "com.baeldung.boot.composite.key" }) public class SpringBootCompositeKeyApplication { public static void main(String... args) { diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java deleted file mode 100644 index 6953a21ddd..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.baeldung.boot.composite.key.dao; - -import java.util.Optional; - -import org.springframework.data.mongodb.repository.MongoRepository; - -import com.baeldung.boot.composite.key.data.Customer; - -public interface CustomerRepository extends MongoRepository { - - Optional findByStoreIdAndNumber(Long storeId, Long number); -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java deleted file mode 100644 index 3caa33d465..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.baeldung.boot.composite.key.dao; - -import java.util.Optional; - -import org.springframework.data.mongodb.repository.MongoRepository; - -import com.baeldung.boot.composite.key.data.Sale; -import com.baeldung.boot.composite.key.data.TicketId; - -public interface SaleRepository extends MongoRepository { - Optional findByTicketId(TicketId ticketId); -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java deleted file mode 100644 index d4bb1ef40c..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.baeldung.boot.composite.key.data; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.index.CompoundIndex; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document -@CompoundIndex(name = "customer_idx", def = "{ 'storeId': 1, 'number': 1 }", unique = true) -public class Customer { - @Id - private String id; - - private Long storeId; - private Long number; - - private String name; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public Long getStoreId() { - return storeId; - } - - public void setStoreId(Long storeId) { - this.storeId = storeId; - } - - public Long getNumber() { - return number; - } - - public void setNumber(Long number) { - this.number = number; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java deleted file mode 100644 index a81c91674c..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.baeldung.boot.composite.key.data; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.index.CompoundIndex; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document -@CompoundIndex(name = "sale_idx", def = "{ 'ticketId': 1 }", unique = true) -public class Sale { - @Id - private String id; - - private TicketId ticketId; - private Double value; - - public String getId() { - return id; - } - public void setId(String id) { - this.id = id; - } - - public TicketId getTicketId() { - return ticketId; - } - - public void setTicketId(TicketId ticketId) { - this.ticketId = ticketId; - } - - public Double getValue() { - return value; - } - - public void setValue(Double value) { - this.value = value; - } -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java index 476209bce9..90ca1b758d 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java @@ -5,25 +5,15 @@ import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.baeldung.boot.composite.key.dao.CustomerRepository; -import com.baeldung.boot.composite.key.dao.SaleRepository; import com.baeldung.boot.composite.key.dao.TicketRepository; -import com.baeldung.boot.composite.key.data.Customer; -import com.baeldung.boot.composite.key.data.Sale; import com.baeldung.boot.composite.key.data.Ticket; import com.baeldung.boot.composite.key.data.TicketId; @Service public class CustomerService { - @Autowired - private CustomerRepository customerRepository; - @Autowired private TicketRepository ticketRepository; - @Autowired - private SaleRepository saleRepository; - public Optional find(TicketId id) { return ticketRepository.findById(id); } @@ -35,32 +25,4 @@ public class CustomerService { public Ticket save(Ticket ticket) { return ticketRepository.save(ticket); } - - public Optional findCustomerById(String id) { - return customerRepository.findById(id); - } - - public Optional findCustomerByIndex(Long storeId, Long number) { - return customerRepository.findByStoreIdAndNumber(storeId, number); - } - - public Customer insert(Customer customer) { - return customerRepository.insert(customer); - } - - public Customer save(Customer customer) { - return customerRepository.save(customer); - } - - public Sale insert(Sale sale) { - return saleRepository.insert(sale); - } - - public Optional findSaleByTicketId(TicketId ticketId) { - return saleRepository.findByTicketId(ticketId); - } - - public Optional findSaleById(String id) { - return saleRepository.findById(id); - } } diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java index 9e41d13e14..4379a46d05 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java @@ -4,15 +4,12 @@ import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.baeldung.boot.composite.key.data.Customer; -import com.baeldung.boot.composite.key.data.Sale; import com.baeldung.boot.composite.key.data.Ticket; import com.baeldung.boot.composite.key.data.TicketId; import com.baeldung.boot.composite.key.service.CustomerService; @@ -37,39 +34,4 @@ public class CustomerController { public Ticket putTicket(@RequestBody Ticket ticket) { return customerService.save(ticket); } - - @GetMapping("/{id}") - public Optional getCustomer(@PathVariable String id) { - return customerService.findCustomerById(id); - } - - @GetMapping("/{storeId}/{number}") - public Optional getCustomerByIndex(@PathVariable Long storeId, @PathVariable Long number) { - return customerService.findCustomerByIndex(storeId, number); - } - - @PostMapping - public Customer postCustomer(@RequestBody Customer customer) { - return customerService.insert(customer); - } - - @PutMapping - public Customer putCustomer(@RequestBody Customer customer) { - return customerService.save(customer); - } - - @PostMapping("/sale") - public Sale postSale(@RequestBody Sale sale) { - return customerService.insert(sale); - } - - @GetMapping("/sale/{id}") - public Optional getSale(@PathVariable String id) { - return customerService.findSaleById(id); - } - - @GetMapping("/sale") - public Optional getSale(TicketId ticketId) { - return customerService.findSaleByTicketId(ticketId); - } } diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties deleted file mode 100644 index a73a94d850..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties +++ /dev/null @@ -1 +0,0 @@ -spring.data.mongodb.auto-index-creation=true diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java index 658be343f1..1aee478ad0 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java @@ -14,7 +14,6 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import com.baeldung.boot.composite.key.data.Customer; import com.baeldung.boot.composite.key.data.Ticket; import com.baeldung.boot.composite.key.data.TicketId; import com.baeldung.boot.composite.key.service.CustomerService; @@ -54,20 +53,6 @@ public class CustomerServiceIntegrationTest { assertEquals(savedTicket.getId(), ticketId); } - @Test - public void givenCompoundUniqueIndex_whenSearchingByGeneratedId_thenFound() { - Customer customer = new Customer(); - customer.setName("Name"); - customer.setNumber(0l); - customer.setStoreId(0l); - - Customer savedCustomer = service.insert(customer); - - Optional optional = service.findCustomerById(savedCustomer.getId()); - - assertThat(optional.isPresent()); - } - @Test public void givenCompositeId_whenDupeInsert_thenExceptionIsThrown() { TicketId ticketId = new TicketId(); @@ -96,35 +81,4 @@ public class CustomerServiceIntegrationTest { assertEquals(savedTicket.getEvent(), ticketB.getEvent()); } - - @Test - public void givenCompoundUniqueIndex_whenDupeInsert_thenExceptionIsThrown() { - Customer customer = new Customer(); - customer.setName("Name"); - customer.setNumber(1l); - customer.setStoreId(2l); - - assertThrows(DuplicateKeyException.class, () -> { - service.insert(customer); - service.insert(customer); - }); - } - - @Test - public void givenCompoundUniqueIndex_whenDupeSave_thenExceptionIsThrown() { - Customer customerA = new Customer(); - customerA.setName("Name A"); - customerA.setNumber(1l); - customerA.setStoreId(2l); - - Customer customerB = new Customer(); - customerB.setName("Name B"); - customerB.setNumber(1l); - customerB.setStoreId(2l); - - assertThrows(DuplicateKeyException.class, () -> { - service.save(customerA); - service.save(customerB); - }); - } } From f1259c2a4d717dc2e3c16f26eb6cae1c065d70b0 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 17 Jun 2022 12:01:44 -0300 Subject: [PATCH 05/42] removing first insert from assertThrows --- .../boot/composite/key/CustomerServiceIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java index 1aee478ad0..af310ab29e 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java @@ -60,10 +60,10 @@ public class CustomerServiceIntegrationTest { ticketId.setVenue("V"); Ticket ticket = new Ticket(ticketId, "Event C"); + service.insert(ticket); assertThrows(DuplicateKeyException.class, () -> { service.insert(ticket); - service.insert(ticket); }); } From e0a7591f9defa207018816003a8b99e192a9b5fe Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Sun, 19 Jun 2022 16:32:10 -0300 Subject: [PATCH 06/42] first draft --- .../SpringBootUniqueFieldApplication.java | 15 +++ .../unique/field/dao/AssetRepository.java | 8 ++ .../unique/field/dao/CompanyRepository.java | 11 ++ .../unique/field/dao/Customer2Repository.java | 8 ++ .../unique/field/dao/CustomerRepository.java | 11 ++ .../boot/unique/field/dao/SaleRepository.java | 12 ++ .../boot/unique/field/data/Asset.java | 29 +++++ .../boot/unique/field/data/Company.java | 40 +++++++ .../boot/unique/field/data/Customer.java | 57 +++++++++ .../boot/unique/field/data/Customer2.java | 47 ++++++++ .../baeldung/boot/unique/field/data/Sale.java | 36 ++++++ .../boot/unique/field/data/SaleId.java | 22 ++++ .../field/web/UniqueFieldController.java | 93 +++++++++++++++ .../boot.unique.field/app.properties | 1 + .../field/UniqueFieldIntegrationTest.java | 112 ++++++++++++++++++ 15 files changed, 502 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/SpringBootUniqueFieldApplication.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/AssetRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CompanyRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CustomerRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/SaleRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Asset.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Company.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Sale.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/SaleId.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.unique.field/app.properties create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/unique/field/UniqueFieldIntegrationTest.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/SpringBootUniqueFieldApplication.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/SpringBootUniqueFieldApplication.java new file mode 100644 index 0000000000..648ecd4dfb --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/SpringBootUniqueFieldApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.boot.unique.field; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@SpringBootApplication +@PropertySource("classpath:boot.unique.field/app.properties") +@EnableMongoRepositories(basePackages = { "com.baeldung.boot.unique.field" }) +public class SpringBootUniqueFieldApplication { + public static void main(String... args) { + SpringApplication.run(SpringBootUniqueFieldApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/AssetRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/AssetRepository.java new file mode 100644 index 0000000000..9adca8b4bd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/AssetRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.boot.unique.field.dao; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.unique.field.data.Asset; + +public interface AssetRepository extends MongoRepository { +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CompanyRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CompanyRepository.java new file mode 100644 index 0000000000..718e284efe --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CompanyRepository.java @@ -0,0 +1,11 @@ +package com.baeldung.boot.unique.field.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.unique.field.data.Company; + +public interface CompanyRepository extends MongoRepository { + Optional findByEmail(String email); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java new file mode 100644 index 0000000000..6afe004609 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java @@ -0,0 +1,8 @@ +package com.baeldung.boot.unique.field.dao; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.unique.field.data.Customer2; + +public interface Customer2Repository extends MongoRepository { +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CustomerRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CustomerRepository.java new file mode 100644 index 0000000000..f6e5b54470 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/CustomerRepository.java @@ -0,0 +1,11 @@ +package com.baeldung.boot.unique.field.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.unique.field.data.Customer; + +public interface CustomerRepository extends MongoRepository { + Optional findByStoreIdAndNumber(Long storeId, Long number); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/SaleRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/SaleRepository.java new file mode 100644 index 0000000000..8547a5ab76 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/SaleRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.unique.field.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.unique.field.data.Sale; +import com.baeldung.boot.unique.field.data.SaleId; + +public interface SaleRepository extends MongoRepository { + Optional findBySaleId(SaleId id); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Asset.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Asset.java new file mode 100644 index 0000000000..9652691a8b --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Asset.java @@ -0,0 +1,29 @@ +package com.baeldung.boot.unique.field.data; + +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Asset { + @Indexed(unique = true) + private String name; + + @Indexed(unique = true) + private Integer number; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Company.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Company.java new file mode 100644 index 0000000000..31b4cf0588 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Company.java @@ -0,0 +1,40 @@ +package com.baeldung.boot.unique.field.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Company { + @Id + private String id; + + private String name; + + @Indexed(unique = true) + private String email; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer.java new file mode 100644 index 0000000000..d1459dc663 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer.java @@ -0,0 +1,57 @@ +package com.baeldung.boot.unique.field.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(name = "customer_idx", def = "{ 'storeId': 1, 'number': 1 }", unique = true) +public class Customer { + @Id + private String id; + + private Long storeId; + + private Long number; + + private String name; + + public Customer() { + } + + public Customer(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public Long getNumber() { + return number; + } + + public void setNumber(Long number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java new file mode 100644 index 0000000000..523a0a9841 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java @@ -0,0 +1,47 @@ +package com.baeldung.boot.unique.field.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(def = "{ 'storeId': 1, 'number': 1 }", unique = true) +public class Customer2 { + @Id + private Long storeId; + + private Long number; + + private String name; + + public Customer2() { + } + + public Customer2(String name) { + this.name = name; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public Long getNumber() { + return number; + } + + public void setNumber(Long number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Sale.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Sale.java new file mode 100644 index 0000000000..3d0a549575 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Sale.java @@ -0,0 +1,36 @@ +package com.baeldung.boot.unique.field.data; + +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Sale { + @Indexed(unique = true) + private SaleId saleId; + + private Double value; + + public Sale() { + } + + public Sale(SaleId saleId) { + super(); + this.saleId = saleId; + } + + public SaleId getSaleId() { + return saleId; + } + + public void setSaleId(SaleId saleId) { + this.saleId = saleId; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/SaleId.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/SaleId.java new file mode 100644 index 0000000000..69a5c5a561 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/SaleId.java @@ -0,0 +1,22 @@ +package com.baeldung.boot.unique.field.data; + +public class SaleId { + private Long item; + private String date; + + public Long getItem() { + return item; + } + + public void setItem(Long item) { + this.item = item; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java new file mode 100644 index 0000000000..8ac00c497f --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java @@ -0,0 +1,93 @@ +package com.baeldung.boot.unique.field.web; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.unique.field.dao.AssetRepository; +import com.baeldung.boot.unique.field.dao.CompanyRepository; +import com.baeldung.boot.unique.field.dao.Customer2Repository; +import com.baeldung.boot.unique.field.dao.CustomerRepository; +import com.baeldung.boot.unique.field.dao.SaleRepository; +import com.baeldung.boot.unique.field.data.Asset; +import com.baeldung.boot.unique.field.data.Company; +import com.baeldung.boot.unique.field.data.Customer; +import com.baeldung.boot.unique.field.data.Customer2; +import com.baeldung.boot.unique.field.data.Sale; +import com.baeldung.boot.unique.field.data.SaleId; + +@RestController +@RequestMapping("/unique-field") +public class UniqueFieldController { + @Autowired + private SaleRepository saleRepo; + + @Autowired + private CompanyRepository companyRepo; + + @Autowired + private CustomerRepository customerRepo; + + @Autowired + private Customer2Repository customer2Repo; + + @Autowired + private AssetRepository assetRepo; + + @PostMapping("/sale") + public Sale post(@RequestBody Sale sale) { + return saleRepo.insert(sale); + } + + @GetMapping("/sale") + public Optional getSale(SaleId id) { + return saleRepo.findBySaleId(id); + } + + @PostMapping("/company") + public Company post(@RequestBody Company company) { + return companyRepo.insert(company); + } + + @PutMapping("/company") + public Company put(@RequestBody Company company) { + return companyRepo.save(company); + } + + @GetMapping("/company/{id}") + public Optional getCompany(@PathVariable String id) { + return companyRepo.findById(id); + } + + @PostMapping("/customer") + public Customer post(@RequestBody Customer customer) { + return customerRepo.insert(customer); + } + + @PostMapping("/customer2") + public Customer2 post(@RequestBody Customer2 customer) { + return customer2Repo.insert(customer); + } + + @GetMapping("/customer/{id}") + public Optional getCustomer(@PathVariable String id) { + return customerRepo.findById(id); + } + + @PostMapping("/asset") + public Asset post(@RequestBody Asset asset) { + return assetRepo.insert(asset); + } + + @GetMapping("/asset/{id}") + public Optional getAsset(@PathVariable String id) { + return assetRepo.findById(id); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.unique.field/app.properties b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.unique.field/app.properties new file mode 100644 index 0000000000..a73a94d850 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.unique.field/app.properties @@ -0,0 +1 @@ +spring.data.mongodb.auto-index-creation=true diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/unique/field/UniqueFieldIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/unique/field/UniqueFieldIntegrationTest.java new file mode 100644 index 0000000000..c18a877b79 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/unique/field/UniqueFieldIntegrationTest.java @@ -0,0 +1,112 @@ +package com.baeldung.boot.unique.field; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.unique.field.dao.AssetRepository; +import com.baeldung.boot.unique.field.dao.CompanyRepository; +import com.baeldung.boot.unique.field.dao.CustomerRepository; +import com.baeldung.boot.unique.field.dao.SaleRepository; +import com.baeldung.boot.unique.field.data.Asset; +import com.baeldung.boot.unique.field.data.Company; +import com.baeldung.boot.unique.field.data.Customer; +import com.baeldung.boot.unique.field.data.Sale; +import com.baeldung.boot.unique.field.data.SaleId; + +@SpringBootTest +@DirtiesContext +@RunWith(SpringRunner.class) +public class UniqueFieldIntegrationTest { + @Autowired + private SaleRepository saleRepo; + + @Autowired + private CompanyRepository companyRepo; + + @Autowired + private CustomerRepository customerRepo; + + @Autowired + private AssetRepository assetRepo; + + @Test + public void givenMultipleIndexes_whenAnyFieldDupe_thenExceptionIsThrown() { + Asset a = new Asset(); + a.setName("Name"); + a.setNumber(1); + + assetRepo.insert(a); + + Asset b = new Asset(); + b.setName("Name"); + b.setNumber(2); + assertThrows(DuplicateKeyException.class, () -> { + assetRepo.insert(b); + }); + + Asset c = new Asset(); + c.setName("Other"); + c.setNumber(1); + assertThrows(DuplicateKeyException.class, () -> { + assetRepo.insert(c); + }); + } + + @Test + public void givenUniqueIndex_whenInsertingDupe_thenExceptionIsThrown() { + Company a = new Company(); + a.setName("Name"); + a.setEmail("a@mail.com"); + + companyRepo.insert(a); + + Company b = new Company(); + b.setName("Other"); + b.setEmail("a@mail.com"); + assertThrows(DuplicateKeyException.class, () -> { + companyRepo.insert(b); + }); + } + + @Test + public void givenCompoundIndex_whenDupeInsert_thenExceptionIsThrown() { + Customer customerA = new Customer("Name A"); + customerA.setNumber(1l); + customerA.setStoreId(2l); + + Customer customerB = new Customer("Name B"); + customerB.setNumber(1l); + customerB.setStoreId(2l); + + customerRepo.insert(customerA); + + assertThrows(DuplicateKeyException.class, () -> { + customerRepo.insert(customerB); + }); + } + + @Test + public void givenCustomTypeIndex_whenInsertingDupe_thenExceptionIsThrown() { + SaleId id = new SaleId(); + id.setDate("2022-06-15"); + id.setItem(1L); + + Sale a = new Sale(id); + a.setValue(53.94); + + saleRepo.insert(a); + + Sale b = new Sale(id); + b.setValue(100.00); + assertThrows(DuplicateKeyException.class, () -> { + saleRepo.insert(b); + }); + } +} From 3ae97db187f253b5773d120b89601e34b57e9630 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Sun, 19 Jun 2022 16:35:36 -0300 Subject: [PATCH 07/42] removing Customer2 --- .../unique/field/dao/Customer2Repository.java | 8 ---- .../boot/unique/field/data/Customer2.java | 47 ------------------- .../field/web/UniqueFieldController.java | 10 ---- 3 files changed, 65 deletions(-) delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java deleted file mode 100644 index 6afe004609..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/dao/Customer2Repository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.baeldung.boot.unique.field.dao; - -import org.springframework.data.mongodb.repository.MongoRepository; - -import com.baeldung.boot.unique.field.data.Customer2; - -public interface Customer2Repository extends MongoRepository { -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java deleted file mode 100644 index 523a0a9841..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/data/Customer2.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.baeldung.boot.unique.field.data; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.index.CompoundIndex; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document -@CompoundIndex(def = "{ 'storeId': 1, 'number': 1 }", unique = true) -public class Customer2 { - @Id - private Long storeId; - - private Long number; - - private String name; - - public Customer2() { - } - - public Customer2(String name) { - this.name = name; - } - - public Long getStoreId() { - return storeId; - } - - public void setStoreId(Long storeId) { - this.storeId = storeId; - } - - public Long getNumber() { - return number; - } - - public void setNumber(Long number) { - this.number = number; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java index 8ac00c497f..716977edd4 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/unique/field/web/UniqueFieldController.java @@ -13,13 +13,11 @@ import org.springframework.web.bind.annotation.RestController; import com.baeldung.boot.unique.field.dao.AssetRepository; import com.baeldung.boot.unique.field.dao.CompanyRepository; -import com.baeldung.boot.unique.field.dao.Customer2Repository; import com.baeldung.boot.unique.field.dao.CustomerRepository; import com.baeldung.boot.unique.field.dao.SaleRepository; import com.baeldung.boot.unique.field.data.Asset; import com.baeldung.boot.unique.field.data.Company; import com.baeldung.boot.unique.field.data.Customer; -import com.baeldung.boot.unique.field.data.Customer2; import com.baeldung.boot.unique.field.data.Sale; import com.baeldung.boot.unique.field.data.SaleId; @@ -35,9 +33,6 @@ public class UniqueFieldController { @Autowired private CustomerRepository customerRepo; - @Autowired - private Customer2Repository customer2Repo; - @Autowired private AssetRepository assetRepo; @@ -71,11 +66,6 @@ public class UniqueFieldController { return customerRepo.insert(customer); } - @PostMapping("/customer2") - public Customer2 post(@RequestBody Customer2 customer) { - return customer2Repo.insert(customer); - } - @GetMapping("/customer/{id}") public Optional getCustomer(@PathVariable String id) { return customerRepo.findById(id); From e5ad2b045b0cc081285e58913e3b0f8bead7cf6e Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Mon, 11 Jul 2022 16:39:59 -0300 Subject: [PATCH 08/42] first draft --- .../count/SpringBootCountApplication.java | 13 ++ .../boot/count/dao/CarRepository.java | 16 +++ .../com/baeldung/boot/count/data/Car.java | 32 +++++ .../boot/count/service/CountCarService.java | 69 ++++++++++ .../boot/count/web/CarController.java | 79 ++++++++++++ .../CountCarServiceIntegrationTest.java | 118 ++++++++++++++++++ 6 files changed, 327 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/SpringBootCountApplication.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/dao/CarRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/service/CountCarService.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/count/service/CountCarServiceIntegrationTest.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/SpringBootCountApplication.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/SpringBootCountApplication.java new file mode 100644 index 0000000000..bb7351383c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/SpringBootCountApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.count; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@SpringBootApplication +@EnableMongoRepositories(basePackages = { "com.baeldung.boot.count" }) +public class SpringBootCountApplication { + public static void main(String... args) { + SpringApplication.run(SpringBootCountApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/dao/CarRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/dao/CarRepository.java new file mode 100644 index 0000000000..b03298852e --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/dao/CarRepository.java @@ -0,0 +1,16 @@ +package com.baeldung.boot.count.dao; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import com.baeldung.boot.count.data.Car; + +public interface CarRepository extends MongoRepository { + @Query(value = "{brand: ?0}", count = true) + public long countBrand(String brand); + + Long countByBrand(String brand); + + @Query(value = "{}", count = true) + Long countWithAnnotation(); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java new file mode 100644 index 0000000000..55c26c1de4 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java @@ -0,0 +1,32 @@ +package com.baeldung.boot.count.data; + +import org.springframework.data.mongodb.core.mapping.Document; +@Document +public class Car { + private String name; + + private String brand; + + public Car() { + } + + public Car(String brand) { + this.brand = brand; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/service/CountCarService.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/service/CountCarService.java new file mode 100644 index 0000000000..d4685847fb --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/service/CountCarService.java @@ -0,0 +1,69 @@ +package com.baeldung.boot.count.service; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import com.baeldung.boot.count.dao.CarRepository; +import com.baeldung.boot.count.data.Car; + +@Service +public class CountCarService { + + @Autowired + private CarRepository repo; + + @Autowired + private MongoTemplate mongo; + + public List findCars() { + return repo.findAll(); + } + + public Optional findCar(String id) { + return repo.findById(id); + } + + public Car insertCar(Car item) { + return repo.insert(item); + } + + public long getCountWithQueryAnnotation() { + return repo.countWithAnnotation(); + } + + public long getCountWithCrudRepository() { + return repo.count(); + } + + public long getCountBrandWithQueryMethod(String brand) { + return repo.countByBrand(brand); + } + + public long getCountWithExample(Car item) { + return repo.count(Example.of(item)); + } + + public long getCountWithExampleCriteria(Car item) { + Query query = new Query(); + query.addCriteria(Criteria.byExample(item)); + return mongo.count(query, Car.class); + } + + public long getCountBrandWithQueryAnnotation(String brand) { + return repo.countBrand(brand); + } + + public long getCountBrandWithCriteria(String brand) { + Query query = new Query(); + query.addCriteria(Criteria.where("brand") + .is(brand)); + return mongo.count(query, Car.class); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java new file mode 100644 index 0000000000..bc5d6dff9d --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java @@ -0,0 +1,79 @@ +package com.baeldung.boot.count.web; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.count.dao.CarRepository; +import com.baeldung.boot.count.data.Car; + +@RestController +@RequestMapping("/car") +public class CarController { + @Autowired + private CarRepository carRepo; + + @Autowired + private MongoTemplate mongo; + + @GetMapping("/{id}") + public Optional getCar(@PathVariable String id) { + return carRepo.findById(id); + } + + @PostMapping + public Car postCar(@RequestBody Car item) { + return carRepo.insert(item); + } + + @GetMapping("/count/{brand}") + public Long getCountCarBrand(@PathVariable String brand) { + return carRepo.countByBrand(brand); + } + + @GetMapping("/count2/{brand}") + public Long getCountCarBrand2(@PathVariable String brand) { + return carRepo.countBrand(brand); + } + + @GetMapping("/count") + public Long getCountCar() { + return carRepo.countWithAnnotation(); + } + + @GetMapping("/count2") + public Long getCountCar2() { + // default do repo + return carRepo.count(); + } + + @PostMapping("/count") + public Long postCount(@RequestBody Car item) { + return carRepo.count(Example.of(item)); + } + + @PostMapping("/count/criteria") + public Long postCountCriteria(@RequestBody Car item) { + Query query = new Query(); + query.addCriteria(Criteria.byExample(item)); + return mongo.count(query, Car.class); + } + + @GetMapping("/count/criteria/{brand}") + public Long getCountCarCriteria(@PathVariable String brand) { + Query query = new Query(); + query.addCriteria(Criteria.where("brand") + .is(brand)); + return mongo.count(query, Car.class); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/count/service/CountCarServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/count/service/CountCarServiceIntegrationTest.java new file mode 100644 index 0000000000..421ecd3a34 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/count/service/CountCarServiceIntegrationTest.java @@ -0,0 +1,118 @@ +package com.baeldung.boot.count.service; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.count.data.Car; + +@SpringBootTest +@DirtiesContext +@RunWith(SpringRunner.class) +public class CountCarServiceIntegrationTest { + @Autowired + private CountCarService service; + + Car car1 = new Car("B-A"); + + @Before + public void init() { + service.insertCar(car1); + service.insertCar(new Car("B-B")); + service.insertCar(new Car("B-C")); + } + + @Test + public void givenAllDocs_whenQueryAnnotationCount_thenCountEqualsSize() { + List all = service.findCars(); + + long count = service.getCountWithQueryAnnotation(); + + assertEquals(count, all.size()); + } + + @Test + public void givenAllDocs_whenCrudRepositoryCount_thenCountEqualsSize() { + List all = service.findCars(); + + long count = service.getCountWithCrudRepository(); + + assertEquals(count, all.size()); + } + + @Test + public void givenFilteredDocs_whenCriteriaCountByBrand_thenCountEqualsSize() { + String filter = "B-A"; + long all = service.findCars() + .stream() + .filter(car -> car.getBrand() + .equals(filter)) + .count(); + + long count = service.getCountBrandWithCriteria(filter); + + assertEquals(count, all); + } + + @Test + public void givenQueryAnnotation_whenCountingByBrand_thenCountEqualsSize() { + String filter = "B-A"; + long all = service.findCars() + .stream() + .filter(car -> car.getBrand() + .equals(filter)) + .count(); + + long count = service.getCountBrandWithQueryAnnotation(filter); + + assertEquals(count, all); + } + + @Test + public void givenFilteredDocs_whenQueryMethodCountByBrand_thenCountEqualsSize() { + String filter = "B-A"; + long all = service.findCars() + .stream() + .filter(car -> car.getBrand() + .equals(filter)) + .count(); + + long count = service.getCountBrandWithQueryMethod(filter); + + assertEquals(count, all); + } + + @Test + public void givenFilteredDocs_whenExampleCount_thenCountEqualsSize() { + long all = service.findCars() + .stream() + .filter(car -> car.getBrand() + .equals(car1.getBrand())) + .count(); + + long count = service.getCountWithExample(car1); + + assertEquals(count, all); + } + + @Test + public void givenFilteredDocs_whenExampleCriteriaCount_thenCountEqualsSize() { + long all = service.findCars() + .stream() + .filter(car -> car.getBrand() + .equals(car1.getBrand())) + .count(); + + long count = service.getCountWithExampleCriteria(car1); + + assertEquals(count, all); + } +} From 8982fdcf737c3e973202ac4474a1c3002f42262d Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 15 Jul 2022 13:17:12 -0300 Subject: [PATCH 09/42] adjustments * removed controlled * fixed formatting on Car --- .../com/baeldung/boot/count/data/Car.java | 1 + .../boot/count/web/CarController.java | 79 ------------------- 2 files changed, 1 insertion(+), 79 deletions(-) delete mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java index 55c26c1de4..42b80d70b0 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/data/Car.java @@ -1,6 +1,7 @@ package com.baeldung.boot.count.data; import org.springframework.data.mongodb.core.mapping.Document; + @Document public class Car { private String name; diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java deleted file mode 100644 index bc5d6dff9d..0000000000 --- a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/count/web/CarController.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.baeldung.boot.count.web; - -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Example; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.baeldung.boot.count.dao.CarRepository; -import com.baeldung.boot.count.data.Car; - -@RestController -@RequestMapping("/car") -public class CarController { - @Autowired - private CarRepository carRepo; - - @Autowired - private MongoTemplate mongo; - - @GetMapping("/{id}") - public Optional getCar(@PathVariable String id) { - return carRepo.findById(id); - } - - @PostMapping - public Car postCar(@RequestBody Car item) { - return carRepo.insert(item); - } - - @GetMapping("/count/{brand}") - public Long getCountCarBrand(@PathVariable String brand) { - return carRepo.countByBrand(brand); - } - - @GetMapping("/count2/{brand}") - public Long getCountCarBrand2(@PathVariable String brand) { - return carRepo.countBrand(brand); - } - - @GetMapping("/count") - public Long getCountCar() { - return carRepo.countWithAnnotation(); - } - - @GetMapping("/count2") - public Long getCountCar2() { - // default do repo - return carRepo.count(); - } - - @PostMapping("/count") - public Long postCount(@RequestBody Car item) { - return carRepo.count(Example.of(item)); - } - - @PostMapping("/count/criteria") - public Long postCountCriteria(@RequestBody Car item) { - Query query = new Query(); - query.addCriteria(Criteria.byExample(item)); - return mongo.count(query, Car.class); - } - - @GetMapping("/count/criteria/{brand}") - public Long getCountCarCriteria(@PathVariable String brand) { - Query query = new Query(); - query.addCriteria(Criteria.where("brand") - .is(brand)); - return mongo.count(query, Car.class); - } -} From 012c924afb48d588274ee9aedce37bd7724e7629 Mon Sep 17 00:00:00 2001 From: apeterlic Date: Sat, 16 Jul 2022 07:46:52 +0200 Subject: [PATCH 10/42] Add Maven Snapshot Repository vs Release Repository --- .../maven-download-artifacts/pom.xml | 52 +++++++++++++++++++ .../maven-release-repository/pom.xml | 38 ++++++++++++++ .../maven-snapshot-repository/pom.xml | 38 ++++++++++++++ maven-modules/maven-repositories/pom.xml | 23 ++++++++ maven-modules/pom.xml | 1 + 5 files changed, 152 insertions(+) create mode 100644 maven-modules/maven-repositories/maven-download-artifacts/pom.xml create mode 100644 maven-modules/maven-repositories/maven-release-repository/pom.xml create mode 100644 maven-modules/maven-repositories/maven-snapshot-repository/pom.xml create mode 100644 maven-modules/maven-repositories/pom.xml diff --git a/maven-modules/maven-repositories/maven-download-artifacts/pom.xml b/maven-modules/maven-repositories/maven-download-artifacts/pom.xml new file mode 100644 index 0000000000..678fc32bec --- /dev/null +++ b/maven-modules/maven-repositories/maven-download-artifacts/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + maven-repository-download + Maven Release Repository + 1.0.0-SNAPSHOT + + + com.baeldung + maven-repositories + 1.0.0-SNAPSHOT + + + + + nexus-snapshot + nexus-snapshot + http://localhost:8081/repository/maven-snapshots/ + + true + + + false + + + + nexus-release + nexus-release + http://localhost:8081/repository/maven-releases/ + + false + + + + + + + com.baeldung + maven-release-repository + 1.0.0 + + + + com.baeldung + maven-snapshot-repository + 1.0.0-SNAPSHOT + + + + \ No newline at end of file diff --git a/maven-modules/maven-repositories/maven-release-repository/pom.xml b/maven-modules/maven-repositories/maven-release-repository/pom.xml new file mode 100644 index 0000000000..bd7adcbcce --- /dev/null +++ b/maven-modules/maven-repositories/maven-release-repository/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + maven-release-repository + Maven Release Repository + 1.0.0 + + + com.baeldung + maven-repositories + 1.0.0-SNAPSHOT + + + + + nexus + nexus-release + http://localhost:8081/repository/maven-releases/ + + false + + + true + + + + + + + nexus + nexus-release + http://localhost:8081/repository/maven-releases/ + + + + \ No newline at end of file diff --git a/maven-modules/maven-repositories/maven-snapshot-repository/pom.xml b/maven-modules/maven-repositories/maven-snapshot-repository/pom.xml new file mode 100644 index 0000000000..5409e47b8e --- /dev/null +++ b/maven-modules/maven-repositories/maven-snapshot-repository/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + maven-snapshot-repository + Maven Snapshot Repository + 1.0.0-SNAPSHOT + + + com.baeldung + maven-repositories + 1.0.0-SNAPSHOT + + + + + nexus + nexus-snapshot + http://localhost:8081/repository/maven-snapshots/ + + true + + + false + + + + + + + nexus + nexus-snapshot + http://localhost:8081/repository/maven-snapshots/ + + + + \ No newline at end of file diff --git a/maven-modules/maven-repositories/pom.xml b/maven-modules/maven-repositories/pom.xml new file mode 100644 index 0000000000..ba39d00d65 --- /dev/null +++ b/maven-modules/maven-repositories/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + maven-repositories + 1.0.0-SNAPSHOT + Maven Repositories + pom + + + com.baeldung + maven-modules + 0.0.1-SNAPSHOT + + + + maven-release-repository + maven-snapshot-repository + maven-download-artifacts + + + \ No newline at end of file diff --git a/maven-modules/pom.xml b/maven-modules/pom.xml index 253f5d9fa0..412e26f041 100644 --- a/maven-modules/pom.xml +++ b/maven-modules/pom.xml @@ -41,6 +41,7 @@ maven-parent-pom-resolution maven-simple maven-classifier + maven-repositories From 9d59e267ac7841e08331eeb1bd6a99800f2f5cb0 Mon Sep 17 00:00:00 2001 From: "thibault.faure" Date: Wed, 29 Jun 2022 11:11:36 +0200 Subject: [PATCH 11/42] BAEL-842 improvement to the spring controller return image or file in controller example: add a dynamic example --- spring-boot-modules/spring-boot-mvc-3/pom.xml | 12 +++++ .../controller/DataProducerController.java | 15 ++++++ .../com/baeldung/produceimage/data.txt | 1 + .../com/baeldung/produceimage/image.jpg | Bin 0 -> 8310 bytes .../com/baeldung/produceimage/image.png | Bin 0 -> 4567 bytes ...DataProducerControllerIntegrationTest.java | 46 ++++++++++++++++++ 6 files changed, 74 insertions(+) create mode 100644 spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/data.txt create mode 100644 spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/image.jpg create mode 100644 spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/image.png create mode 100644 spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-mvc-3/pom.xml b/spring-boot-modules/spring-boot-mvc-3/pom.xml index 43a492786e..f2b6c129f8 100644 --- a/spring-boot-modules/spring-boot-mvc-3/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-3/pom.xml @@ -40,6 +40,18 @@ commons-io ${commons-io.version} + + org.springframework.boot + spring-boot-starter-test + test + + + + + /src/main/resources + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java index ab233a8b60..218be68a45 100644 --- a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java +++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java @@ -1,9 +1,12 @@ package com.baeldung.produceimage.controller; import org.apache.commons.io.IOUtils; +import org.springframework.core.io.InputStreamResource; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.io.IOException; @@ -29,6 +32,18 @@ public class DataProducerController { return IOUtils.toByteArray(in); } + @GetMapping("/get-image-dynamic-type") + @ResponseBody + public ResponseEntity getImageDynamicType(@RequestParam("jpg") boolean jpg) { + final MediaType contentType = jpg ? MediaType.IMAGE_JPEG : MediaType.IMAGE_PNG; + final InputStream in = jpg ? + getClass().getResourceAsStream("/com/baeldung/produceimage/image.jpg") : + getClass().getResourceAsStream("/com/baeldung/produceimage/image.png"); + return ResponseEntity.ok() + .contentType(contentType) + .body(new InputStreamResource(in)); + } + @GetMapping(value = "/get-file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public @ResponseBody byte[] getFile() throws IOException { final InputStream in = getClass().getResourceAsStream("/com/baeldung/produceimage/data.txt"); diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/data.txt b/spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/data.txt new file mode 100644 index 0000000000..5a8297fc5e --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/data.txt @@ -0,0 +1 @@ +Hello Baeldung! \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/image.jpg b/spring-boot-modules/spring-boot-mvc-3/src/main/resources/com/baeldung/produceimage/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db3abce0d6ac5d8088ae6ddc9d17037e7465198e GIT binary patch literal 8310 zcmbVxWmFu^)^)?+?hI}Nfgr&pxI+l;9vp%@1PKx#xC{*L5ZqltaF^ijmY{)2&;(xc z+~>RN{{Bu|ov!X)yUwXyYggCf(&H9@_exGt4nRNv0EDLvczgh8q@5jI+^pQ)tt@HH zeQDpiSlO_P*9LjP|#4H3=IqYuVE2l;b1-Og!n{wxOhbP)TG43 zq}23O6jb!j85y7Ra`Vc_7$N-keWd@t4?Ok)_-H^Xf(i(M9zeuL0O2D%_5)N;EkHyB z{cX(uGgLHW6a*x65aQzsfcBq?FWtaR;6u>gCs-eFCjVYRACnTVMOpX$7U4v)D`JVC z;6sHSE>Aw%CG-e*hf5dq6rgyxAJ(b!N*vKo`@dC)GzFb1qBo>_4uMd&Cm%^(_H<1a zJ8UNB$pJjg^|-Nq!zUjBTkE_hKYY6~EP2_5tNA$N`+=WPLf|T0+UTyW1b|HX&71FB z8mv0$IyI%XChid@TmJ`()ZpWt%wcf3V>sbOwi}VD=PIjdp|*MLHH2zmU>Yah?N+;u z(5J}MxC2{ozj9xFMDh!jhGJKfd3``j8RB*rI2@|n3@`jDdKKYdFjr_-fn}VMa9j+n z`(=QV>INIlwcP69ErpFr{SV_=Xy;}o-MMTEE`nEYj1?n!m}eRLWFiaB?d-5YYnM#98znxAepDe? zwT^M2kOZT7T}XHm)ATjRr*%0juluXDx9B>>LWD&pK?0NJEt z{8E|pa~~%Nbs}q3bS|^~tKFCZW>Z^-@L~J84LK%fv)f%;dQFPCvvX}g6Dl{GJSxEu ze~yEQttkLlxYsQKfcO&(ihp7J4~{1oD2NEC|KI@qgCjQ&Kp+VW#QjFy4UFyu$isi4 z2LXG#`N$i40NJHt#DQZ;8LK|vM)T5Nc*bTaBTRXfx9n&-+F&;J`n=@etx4tg+eg44 z&6q~iYid1$;aaLBvdcI;%j->3TZDmV)f)geYcVeADNcVQ!-oeTAR!_kf>4l zl#EwQcIegitMV3G#8z?8;n3-61cty*;#M~4=J?mHj`Mo_1`{D|&-U(;-V2S=m6!G^ zZ~7sn*f59ziYZG1c8x6QtXyL@;9l35XW&7hEDOO&o>BC%#G-jDgWoCvAajS-C_IJh zGB~$a#eB}egHcwJLE7!F7e5v~Rr8cbV^IkiIZWJxPk2g}KdELx-Jd^O!ebgCSW@rl zREHclb>l4S`cBi;`>Lf~)L|1{8eZ9m)@27zaJaGaV0`g*L&z z;iLSplpH%kes67jOz{TVw}s+{^IYRmv*!thXUY0FUGmL^CT{bV5y4VC$#g7I1j)N# z)${8-j+Cl>V>=-oIdCZ28%eP1CErz1R0--$^+_{P;MB^Y(dIiQ#(`LqTaTR9z*@{^ zvS#kJFJBqW!_y;^qIsx>=G%)5q*;?w#$;8_L^CQB$Eth3Ol_{CpteMCuPFKxgQ}hG@JjU}&k!Cz znt1#wLUPl6*IFDFvq{Ddn8@-MyKjWMF8uoA1QQtlcGsi?Q8T& zbN#=4gg!QFADUcqk$`uN4tQdIuWtS#dhob63ym-zWvgrr?r8Or)j!HgdB^nH4)jfJ zK-H+CZm011)P9`fX_q}ys#aMFOz?w<=AVQJYTD8^pYg`q=?G-#)j)<0tUQ0ev*pes zS*U`y@j04q!cxl*W|wrnIWsuh!pN~}+si;n=ff@c9Mg#%0{JnWQ=WN-B!8rkL3iU5 zz}dw|2=-^>v&6gGE|2Bk3{^&}hN8SdD{;}Us4|nF329s-WEMGWO})wxXZ}4%ICAh3 zluLWLJYS{NNR(kA1;>+u^L=h`uq390sDN_Lor6n%Qj64~`YZwSOB7_JD!^l#!>@0- zfSS>s&|G;Yr0h%a>n5 zQqwk5{R0bW23S~5!)(9@Bw;p!uj;anl%d4(T;`+FT*2oga$9!p{P?DFKC6~#(hiL& zT{>NhE%WZRfzB}1!?)^_|d=0aqzRaL3m#WRz9zmJ`C_`0{YQ-_i0 zMYk#Oj;MOeFNkrb0S3|3m8!IOOlI$cF|KPwsfh0RhC@tm2e;zsHM`7g<)M@q2Ab4{GH=<-6sEdW zh{wfoI*#E?qv7e22~QXbRvoipVh{PH7@_qeFqXQL&la~!EvRp`YoNoXvQeROfOWNe zg!)9Y60E~*{u7Is_fAvH$5tzj$__SU@&XLOO;we*@2S}|MD5fL)mtaoemp-^_B~~E zx$-36A+`&V-8(8CPSP>uCWF#(rJO-w#u31)n7o;m1RzndvlzcMt!U4 zk+X5WJQrR0Jg{+wB7>FP6KK!>d1ZJiAlv_X^oZ(Z~6d4}|Pqp2i_N06}bG{-{k`#M!y-MpkCzPvCZUxJhEYdMr=>vA}SY2qAgHp$J z2gW}4%ra(4yfMwhu?{`K&d(Wg-Go^`3G&o0HA}Ff0Rgwt_E80=5n@=?>Ol=k4Ljn{ z;Anv{;%8mMOfX|qSDvW<=1a#%fXx12xazY`+v}k!vhF)?N01Rdu^~^nE%JIfc6`#J zO;jI6gk7}DH76dKI~P&BftJu*vf<~W28AK7CgbTS<+8%~-Y)WveZJa2`U(*n*wRA3 zF?+-WXBe$YRzbEoK@azC0Q8^0$?B;z)*PnqA1=Tq)hi|pJbY?Z_64`4$-ws8$q^_5 zCTAaok!*YP;;qH3`x?qpx?%b}(ggcmUjqjHmv?7ByeK(0`3!fevbRaEt23_|V!INb znTUJ@hBJG9F%2<}^GB7TO@O2hc()HYkZPr~R>{3dO0w%0F2jq9Do6yqVIoE7yF&OX zjgw-DJN83j4e#D(VHA8neF-Ae)bnCFX&MT262to+#YJ1U0Glcs15CrH&CvBa;Bb@=#-#vn1hEQ$l(8gV}<-3n*Dx+3W2- znoQ%%UYuRcxE9@*kiEH4}+wsEbZot)&Z%O{qN|x$qB^1 z5E1bjf-RLc$~&s}`F~nce2SC^qOVZ>5b}&=M5ODeqqhofCEwHTk_;xWC;A4azl`T; zZ2Zx}SnY(q5S|4q)8E?EoB0Kg(9(W2#Vc(gvp5ytUG7KZ^=1d19ZBWqH&Bhifu&1~ zRqpqv(IdO=^65+?;NadYw)w&#pbnm~`;90CV}k@vL48NrejrXj75e5~Y^;kVpLJ+J zl68Ajur1#MO$1vgW3gq)K4uCuK!&Q6#c8&@3Ubg%kSp8yy?nlBG2UJ%4kj*PDr=aA zIZV0KPGg=>J%xmqmS?B%HXmzj^h>0WX~|!)D2)doB7hJ;sOZSZsK|fC^^_1pW>d&J2V!$__#uk9-ktBW|~6!m83W8K@d zd@X;{1!KhpDrqqLoS0MbKS-_>xJFP=VLJT% z_&uUug+k^wA?vLayw#t5ycESfe3{(4=^ZI$wXPNvzkw@tf16o^4U(^{210u_{rphQRt&vq`sly&F*OlX&xNq} zSe!eayqz>sq^C-Cf9C9OcXXRYYFGFOsQY6#NBG4dG=@+i2`)|7uihI|#W)%lBzOni^^^`1O43e?uz91Je)Fbb{CZH_rnN{f z6Tq3qfYoR1$0F=x^QP^F{%d2V_CzwEpyH@Dp%Cy}F1zO#=zV&@G zjD9h2S2;y(um-xhtbyvd(O?7@G^l5`Ef~&LRov=nyeC&&D?DMk&e&*;`1?eaWhR}? zWpn)?jtj$e2GM4zvQ~ABP!i#WdTT zoGc>aM?U6%WU1YAy&z;`3uoCZAyH6-~;`ym}V0$XJEHZj@yvLf~A^9No z)P2^DY3knQHD_=*bHMLbgmpFaZ7!{%@S|4dyEI&$s+6&7P}v)w8R z-F6tGpuXjcV$X}&s0Ks*s_79Y+p$Vc6$}qw>iM(cNOVM9_TZY21`Xem3>gSyr3&59N2%S8at8HOE7gxM{I@n15zZs(K2JR*k>FH(R5`fXOOF z+dw{QW!Dd!C3eORrXivUA(02A)j02pJDWPF<%~O7m!;Kd>A>{Cr_Vr)v2Sq+Rk0HI zi-nLegWLq8!u8+DcDq5^KLim7DQw2JU6&i8J0T(M6IgnXL&K#hZ`W}azZVWhfOMUZ z){5~g+`%UcIPT;>4C02H8p=k~nR;dXdaQ|EnPrgvlrbjOqNv&vYRG-&{u$Hp#a1c+ zJoKhat0-|aczW&}iRyzc^Wf}Ci2Fo+mLlm|8gp$rFp?Sic(Vqdcm43sx`|IVkMzjr&SLAv9#TiBs6G+#tgaAa)s?He zE57v9zhKSSJ3r?0Y&wj3d*@paqJhBGg9#osA+r)HE2Ps4E*JP^se)DFX{Zb#KOxVe_Drk=by;PjVOo5=q=_PE`l@OF4|+{S$>Uj^2} zg~D+~Ftfnoo$n)XEA8cI<2HF;Q9Ldq^ z9G22E3!5{PU$6)ksSauWi#<-gURnsGh`jwa!Ks zlRmIK$K)MmP;t9Rghd4Pt|rjaZlu?9yI5(SAnWku+GSZBjhE4uDJ-sv;1fE+XS43b zzk{9btCl-#Wgg>|W=?YSrP`rhw?;;rhK?S~2>p>b$+NvZ^4SVgrD*S6HRZ_(VZ>}E z*`<8R6$XRj4qPf*k7yyo&@M>)_qXjISJsosxXNfNgRt)u@-lv5CO@MZ#c`J;SeX;- zbLVn;wgbuN?1)2)CvG+_^0peTZtlEL4xE1k8lFL`&P6nJb-WcRIENKw`x>2pS0YsC zX9$rpnh044F8QG`n3X0#I&bkMg{V6n0yBEJR`rLW6Leeoss}F59Uy9DqkS0fxI&FJ z-FM^bB|%X*fGg7gYa`&72pVv8SUzbZqlhe3;t2J zjs!*(t4z+k9rK=%Cw9T8Jx9p|>2i+XO?_tACOxoyo#dfl`1Bu|u25=eo{@sb28o-P z<8IwJ7efV@)`+h?4rX)1o{{q-d(vMAqdW||H#bQTQEL9!Xix-$8Mf}V5^YeewL&65KAMEUROKm}JyVmX z^3#cVy&$-EZB=fzd$%@1!Zimw^tB5Kg+Arstt^F|Y;m5;)D-tNwY5=D-suc0^t`Y= z!`o*WS3bZ@Q<1SLQ4`S60y;IS=RKz|P*04P24@*K{=ANcsr>%BQGm*RW8QGJ-!28M z-KJD-5k1wZL)(?SN%Co+D!R(}+?jNMzT+_+8CS_=&YM!xcYS^z0tYa2Dg58EcH&|-&npb^p6$l zrbp2m8e#yVgh$GDVP}s(24o1yL8_1ZVy!=;d9MLogxXiDCOjZ2p+hdhU62CVJt8|w zmmNiNC7k3Qz@Rv&>#q|JOr?W;q0auI|$Ux@p=RaB)5dlFQxIg%g zJlyhqsOCk1FGGyg+Kcp0D#y1{b44Ut%*4aT+^l#+d= z#x(p>v)-Q-*ZS|i>ho7I`2AYO!a~p%0-+T3*2Dr zZlC$i9iRRn3t#Z5?d$7$ISMk1uHq0wQbp~a^s*YC?$XxJXii*H$-$LF2NF>MzWden0bnwa=IFt=jw$(Iw$2o&TD z1*;0l!F;=&MQ;`+Zq07v9YS_k+ZYu_&MBQB&Yuj&2Gzbv#ib$Eu#F9neY7exvPdiH zYSYF~^Aude!FdaI5JHJlH`Vg(k6XI$8C~;(%{<`Mi>v3d+c{h@TNT zniw*tf)nK=_=xxWF;8;n71p?M>Sh4Hq5>uR16nKZ5j0!#SRTe!Th4T>)7=!nZ%bTwP#rE2(p8}joSWh7T~2NRQ{O{@DL{QQSLLjTYQ5d=a*M*{s1eUP6f zmXd@|1R|!FaCHk#Bw~14gOJk5ucPI0?+Hz+|Iez)Uj{uil*+vb%Y6qwb2`_6`IwQx zwAFX)eq}=TPBxC_CtLMjt;%WTj?Y0{X;WP{JG}hg3T%=K{vO4hn&VS~+mRQ&Uk@|^ zb>+w>Mi1|&{bEWYvqVk_z@Lamfb&YAlA$i|wIE3XBK`Ki--S1WH)wly!2|3?IqmS z(C)d~!O4cS>k+WWU(*Sr~B8&sNjBjA%&-ceedbBJ=zZRuBB%V4ad2 zR@?FVW+E+(qUoZ#tO~5qy^J$VUxp~%KB4WtlYX_air3IxGw(^2=LgJhz63jDifH{1 z>X~?&&e)OKvs@vJXTVPeS}@ftuCnF~`ZZOtSjhT*iDH|+b=A)q=eYH2$90siRsefl zEosi#{8^}>S1PGYY}U?J;IrWMy0hdSUDDS4J-ukf{!6Dj?;Mi5QhiQvc`zx1gr~(R zinA@PE*oiVsD>>f$cJxko zX^eSgtOr*8DYPWXx7OH8s{yO^};+qbi1o@QHiG?x|gsc74 zcrk6!$~&z@l_V6TElQnYuJ)W^YP5?&KY<)RO7#Kq!Hi99V2+I&l9HVDPmz91D=M+X;@`P!Lu` zBKMeUdL|3wF2&VeDQIHn%Rze$dVvu(r16~H;1`~h$sjdP!vc#OHWP zUDzVOuJz@-HytDa#Wm;6^t^LPznY<=`y8UZden}x5K;B5q{gZ&-v{V-ukS5{a+rYs zJ1z2*(*jKI7B=0GJv>sA~-8sG5{2iaKr?(%zcYH$_+D*o^B*`mPU5g|6kMuDwo1$ARC!Q1L&fk;U@`=jM+h`<) W&N(9HN76>pOMWXIs(2aPx#1ZP1_K>z@;j|==^1poj5oKQ?uMgRZ*DSI*|dM_JxCLwq$9(O3p+{_bnAbhHT zjIfXA@aPwHBRqve&f(A>cPjMw^ayep)#287qwXru)oy7khYZQ@#)*=+@8Flg|CUc(!Kz02~3VtU6f)the4;qs+zo=j<%9Vj83%7 zwo#E+Sd(3(#HTZVJ7u0~ccOY*l3;0?ZLoD z5Qj-bK~#8N?VSsEqP!M{C4f<51=}iCt<>9SwYL3E|NnnGx$GShE*8-_XXb03wPvkB zQZ~G~?d&AbayT3ghr{7;I2`|0hO?K>>EW=MzU)>J{N`vzx(q2}VL(VQV2sj--3ZP& zE+(A@KJiQl{aE(ktm87?NeC($5avC*I)6D)C$UQ=Lg-@~(m3i8Z5YT`gsk_F%2AVv z589OyFCm$u7JCZ92}mF?t3C-*Na(0R8iQ^e-`||J+wD`I8r?iSLP|#sP65c~1@sj< zo<(~fFcj2(RX;jvaMXp9o)++%WEVmQ70=-p$G1cqES&Tne$p(da7Nk2`JT0DW8w7w zfnN;LDNESY4UOuvg~Cb3R!v9Jq3hN!p!WeC4m6PIzI*?Rvzh*jq)`DXlA5E zdM|6deCWsPY4Ns93p235?}e77)ucym%;1vj6+FX$h;h@a(DLV5n0ttQOnpWt1(n}@ z{(HQIBgv@iYd=fE{5P}J=uk@ICwZkEp=Qc}S_!$>fk=C@tVSK*9u^PSuUu?FTG%QJ zo@HT>w*aKG2QOg!&rHUb1u4g1$RF_=ums?OQmYqA6ZtF6k2zBnK8cO7eY;-_0tDeZ zoaJ>w<{Ry_T6d?sRWBe}LY^@yTP&+-NsNK`9aC|`!c)P*fb1YyMtj1uHJ{M)!V{dY z2Mj04xHT;agZK|d9qp=uOb)r?3BL!K;rCFF3=Bxph4Za9d7gYk^RkqL8QSesjUoNa z>K!S&yvLr5%m)koXzqV!K@ctD0i4URzNmPDE%Ip%+%*Bs>cvE*C6Bg&=ma^^$EVp9 ziUW|mKnf2MyjH{oMX3T_AeV{G*o9ZBrYLlop7=DC7N(F;5^YMKUck(%@*O>lC4E!` zmB?D_`2e!z<%IS^%bD7(rUCIF3CdX#vQ{Lz<>){fAKS)gQ&En1=4VA1t6HT6vu%nV zE0cM?u2MC)E2tyn#b}K!XPJdS_W|>gUPE(@ob1$=S}o?A>_iORU`;u* z02RBJ4aI1eHFiv0%aoI_x4EnUQMxzkCViGEi*i1xi?RMbRW~wfPX_vW11NK*Lc)T) zH(|k$9xSwQBqQOj5@ReYPR->ciQ#Cz#At-{)os_4v$vK<99>dcJzf7p)yEie5^A*d zqlvD`WUO+hAm?$-|JaZBx|!s($EcDVj&KY)1JgpZ)s=^QC<{1};cyrCZMK?o-tdBr z%|xGO9u%GrvdoS2*u-YmGknVc+dp{p(Q<*`o6jLu%SSxx0E28p!>OrEMGXN0#A zU=JoqQ9xCAw44+@QB8EfbD(bfHYf`yC;25$S`6{I=!_qsCz7k3 z*aS3`({RHy7#~qNUQ5m%Jd~H%b!>^M$h$*1p`*7sK)FyETwBgP+^oubl=G~G;Ef;b zA*nG)7F;JM3E&r#A=ZiuR%n6p11vMaf7D6fIyuAA*-Gr*Vo}sCk~6U4b-W6roU*?MISG;Ha`fPW zDibX^qc@0~Nw|NQfe*rS!H~18H4&qJ!-6iIx0I8KW~2aKQ#LXe$vMc);|-xjwB*dy zF9b7D>SD?Hp1FwCB;x)+qmmv$fDQbbTR_k5rj=cUkBjKwvgm*>l2gf+fpV(n134=u z@Uh#Hx>$1J!C4Y*OH;Vd#2>X**eFF3GW)Zfu|zLr(? zzTIbEA!qO@&*iCZJG0VBRKyJ-MpV#jJ8joogBC>FnYn{Jd&RNhx>1qx*7AJ9HRYl& z$kJRJ(~3Z-26C47yAeisiaon7oqqo*$5@LZa{=UpabzB}AYHL6py1I%CtV0eJ4n$` z&a%;iQBH(Xs4Wu@mVslZ26B2#p3Bh&J0)`BV1VrwSvgYlp5YuB8*+Msd^*7#pIZ2x z+=R{vkA!h7+^!h^zM-6|isWcxVUQ1o&qD3VXAC|xkyAQgd9+_Gn*vKtJQbISlw$ps zvuY>@E1N0D%a8|o7>zLlOK2!(S#8(RA?a@8P2}1m^xK-qS>|}^NP+72EjjPtDrFuP zCRDu-${8HwNgM3T(X$x!wG0Is%1HvDhJ}sKSR#WOonY^Z8G10;DJPqY62+)c!QUIZ{Bi^EVPN0X|T27ox$j~7xv!9whRus^tau&7}EI}FWY0Bdf9$OWt zi#Og#PS4Mc0*Bys@iXdnY&rG3-T(_0@QNqEao2Ks;iuiu-f81t0oZ||9F65P7Yp#R zImH{)p-b&h7;7lzP}Q5t=`rhRkJ*xZ*x+Ex*}@eOWNqpBiuXnWRTwila~5XWg1v>} zy`np6E~m#1`W{zfomm6yFYw8P@KA%^vzgY0a(aVoK$cFj^&>ds%gG6AY&dbY!?FFQ z)x+sYLr!l%$}O0M^VMiP2_F@}f3K30yIq^tHs|?Z$P{8G>Odqn@-dyuWN^%BDyK)- zV~1a?y*nB6!g{JHC#P;B#XUUj#LezEf&MMPh3BT66&B2y#vQh&UKo?jcJrZCzeY}O z#HVp=?t<~+Y$S_uEe;t_Y9(tfC;!t>7-i-n4V>IB$l1<}l7PkH?~J=*IHzFAS!%(Y z1<(3Z(n-@%H0rf+;M7pgil)P3#uVBtE1pG~$XR97WBSkYa<*o=AQj0ZLOH7~m|FpG zTtvpYwYzfaZIV?36ZQlt1?8));LhvGseT7J$-7?TVB=?23v#x06@SDBZ&^ixn+o<7 z;`^|?K@wrW&Tvv2MU>DiANQ1wv~p{W7axU0WB~IPL^fNv;6n}dGE&GlHD&=h{kvhQ z`%I0Qk)oWfcd-tn2r-(amQs zGGzP#F@G{STWxUz284~RxJa3tt==}TQ5f7?8F(iaZ3WU}Za?NNct$TIz`=o8rJBjv z8ZBcMz$T4-!f-7b@8Qj0s=-`ueIp6x+lshr&8hWFFzjQFc~l3ymk6X;hPSkG0kV9dT5jZ8y1ZFcwmnmZrK`e9kU z2js5XUnfbjK6Gnr=qbgNlQZiuW%XClYK5FUdtN@{ME=#d^X*GucAc=&0+$|(Z+LCn zStLq_X|J?gnk;&Sn9c>{jj(oxb=t*6?*1YQU8&^gisxcUX&A4oTIxj(X@Q` zrd(q1hw~|GaOoU)o=81qaq9Bs9LL69KOi?H^@oy%At6C-IY|X8Iv|7{z8gkf#G+&) z0MEIXP0R*j`IuTzHt^$;Akd#kd-C|Wt=THIxBX4$hE;trpN=c~mnhv&w}*bezd3Ec zzNX97O*8R#vbaE?!uqbpM0ec@p)CJ~1B+2_aXW+mTB74i_hB9T0rCEd2JufIov&8= z>Dw<(|MRo+ESY|e6AooC_R03%Iw~0rcW;j#qtw^EN$5-Xv6K-A)EXg2HBtNiky3N@ zMkORD?baQd#q&c_9mL3?m*}RycbxUNvgpLta+LGv7cxb^avSp;oal88L6Ta8db7yZ z_i?t+fvig2Cw7MH)j;<&x4zRMmh{%I<{Z6hF5+r>6}x19E+?Ebs|N|te-~>0_8974 zMpU()y#k_RE)$gv6+1S6CQ%=z!fQfskI@KU5>>vR{)e);q(|r4;f9m6um4bPB6JAO zy*RD}t>(~JR4Lyg;o!_G7tNMJ0;uV$fe~`A*iirTh^%#y-x6S-zO>foy1ocd%VFyh zJ}Q4Rf-W6#aw>)wjfL2o8Vj}ao|ThRF}cJ?!cBVn!i$!joD%)MtQrv5qO{0fHK}oO zN^qr34MID`35n&luvS`pJ-xumDbPFXza#Xdw;8>V^0wA}CrnVY zBO>fej(S#ysxY2&E{sCH=HZ7W|);l}I&z0u+vf!f)YPLgUbK)%28$Aj;9 zIYSVCihACj_~+V%dTY-9?R@(tx_~~-c?Z-!g=ZUC>jeQnmPo&YubjZ&@KQJ0Kb@)O zl2AEOKb+|71W_12wOg&(G|^i&E77lJ_|fQB#1|oh!7^POPf2kdaS`)70Y4bMA+-QG zb&ju($klR^_%GOJ`9I@YIep?*dKp|N=U{EWL+QtJjhw;SeQiF+8Nc&9 z#P?Nr-eG_{+%JO!@nPx2EMk1uv@V3P@#oOBnPsGP<~A|{pRR+_ukTY-=OKhq|6{YAzOHJ#NY5d_x9t@OnD?-6 zw_S<6hzd_BTz|P%vH|)tmnI=wx)m~wh>E)mNa6;Mjmf@j00d=k2X@pXzpBRHxT1Ll zsa-m$Tg`DDR&BSw3*=~k83plO*}Rq!esR+C2IfaW9L>-nWVfjB7W8oKOzb?QTL8r~ zEdTzx<3dKt^`x$<--c(QXa1{5j;2Indy9Kp!p-2mC#m!DWJf)=Vd)379nI*CiL2{3 z~$H(Vs6SltN7? z4gBL?0h#_|5%8gwmX?;5mX?;5mX?;5mOIhu{2#Y65H^G1m%so3002ovPDHLkV1k2+ B)jR+I literal 0 HcmV?d00001 diff --git a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java new file mode 100644 index 0000000000..29f794645a --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java @@ -0,0 +1,46 @@ +package com.baeldung.produceimage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest(classes = ImageApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class DataProducerControllerIntegrationTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Test + void givenJpgTrue_whenGetImageDynamicType_ThenContentTypeIsJpg() throws Exception { + mockMvc.perform(get("/get-image-dynamic-type?jpg=true")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.IMAGE_JPEG)) + .andExpect(header().stringValues(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_JPEG_VALUE)); + } + + @Test + void givenJpgFalse_whenGetImageDynamicType_ThenContentTypeIsFalse() throws Exception { + mockMvc.perform(get("/get-image-dynamic-type?jpg=false")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.IMAGE_PNG)) + .andExpect(header().stringValues(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)); + } + +} From bd44bc5f2d2ca7b7fe33aff4502b3a75094db191 Mon Sep 17 00:00:00 2001 From: "thibault.faure" Date: Mon, 4 Jul 2022 22:08:12 +0200 Subject: [PATCH 12/42] BAEL-842 fix failed build because of UserAccountUnitTest --- ...ntUnitTest.java => UserAccountIntegrationTest.java} | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) rename spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/{UserAccountUnitTest.java => UserAccountIntegrationTest.java} (84%) diff --git a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountUnitTest.java b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountIntegrationTest.java similarity index 84% rename from spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountUnitTest.java rename to spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountIntegrationTest.java index 507321bf8e..7c23153951 100644 --- a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountUnitTest.java +++ b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/springvalidation/UserAccountIntegrationTest.java @@ -1,9 +1,7 @@ package com.baeldung.springvalidation; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -15,7 +13,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc -public class UserAccountUnitTest { +public class UserAccountIntegrationTest { @Autowired private MockMvc mockMvc; @@ -46,10 +44,8 @@ public class UserAccountUnitTest { public void givenSaveBasicInfoStep1_whenIncorrectInput_thenError() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1") .accept(MediaType.TEXT_HTML)) - // .param("name", "test123") - // .param("password", "pass")) .andExpect(model().errorCount(2)) - // .andExpect(view().name("error")) + .andExpect(view().name("error")) .andExpect(status().isOk()) .andDo(print()); } From 735f657223734e017187b4f9b4dae7e930624af4 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Fri, 22 Jul 2022 16:06:46 +0100 Subject: [PATCH 13/42] [JAVA-13130] Logging cleanup --- .../src/test/resources/logback-test.xml | 4 ++++ .../exception/detachedentity/HibernateUtil.java | 3 ++- .../src/test/resources/logback-test.xml | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 persistence-modules/hibernate-exceptions/src/test/resources/logback-test.xml diff --git a/libraries-security/src/test/resources/logback-test.xml b/libraries-security/src/test/resources/logback-test.xml index 8d4771e308..fcbdb8b5f7 100644 --- a/libraries-security/src/test/resources/logback-test.xml +++ b/libraries-security/src/test/resources/logback-test.xml @@ -6,6 +6,10 @@ + + + + diff --git a/persistence-modules/hibernate-exceptions/src/main/java/com/baeldung/hibernate/exception/detachedentity/HibernateUtil.java b/persistence-modules/hibernate-exceptions/src/main/java/com/baeldung/hibernate/exception/detachedentity/HibernateUtil.java index 0420755354..a7b1f496d9 100644 --- a/persistence-modules/hibernate-exceptions/src/main/java/com/baeldung/hibernate/exception/detachedentity/HibernateUtil.java +++ b/persistence-modules/hibernate-exceptions/src/main/java/com/baeldung/hibernate/exception/detachedentity/HibernateUtil.java @@ -24,7 +24,8 @@ public class HibernateUtil { settings.put(Environment.USER, "sa"); settings.put(Environment.PASS, ""); settings.put(Environment.DIALECT, "org.hibernate.dialect.HSQLDialect"); - settings.put(Environment.SHOW_SQL, "true"); + // enable to show Hibernate generated SQL + settings.put(Environment.SHOW_SQL, "false"); settings.put(Environment.FORMAT_SQL, "true"); settings.put(Environment.USE_SQL_COMMENTS, "true"); settings.put(Environment.HBM2DDL_AUTO, "update"); diff --git a/persistence-modules/hibernate-exceptions/src/test/resources/logback-test.xml b/persistence-modules/hibernate-exceptions/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..5d5620b2c1 --- /dev/null +++ b/persistence-modules/hibernate-exceptions/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n + + + + + + + + + + + \ No newline at end of file From a4691406f2b391651ca0826ae5afb2f4501280da Mon Sep 17 00:00:00 2001 From: victorsempere Date: Mon, 25 Jul 2022 05:51:00 +0200 Subject: [PATCH 14/42] BAEL-5328 (#12457) * BAEL-5328 First draft of the code to the article: https://drafts.baeldung.com/wp-admin/post.php?post=136328&action=edit * BAEL-5328 Fixed comment. Used entry point instead of endpoint * BAEL-5328 Added SecurityFilterChain configuration to allow requests to: * "/api/auth/**", "/swagger-ui-custom.html" ,"/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/swagger-ui/index.html","/api-docs/**" As the spring-boot-starter-security has been started we need to add it to keep this article, simple. Fixed description of the login() method for the 200 httpstatus response * BAEL-5328 Added required attribute in @RequestBody of the login() method * BAEL-5328 Code formatting --- ...efaultGlobalSecuritySchemeApplication.java | 32 ++++++ ...GlobalSecuritySchemeOpenApiController.java | 61 +++++++++++ .../dto/ApplicationExceptionDto.java | 26 +++++ .../dto/LoginDto.java | 103 ++++++++++++++++++ .../dto/PingResponseDto.java | 84 ++++++++++++++ .../dto/TokenDto.java | 57 ++++++++++ 6 files changed, 363 insertions(+) create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/DefaultGlobalSecuritySchemeApplication.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/controller/DefaultGlobalSecuritySchemeOpenApiController.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/ApplicationExceptionDto.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/LoginDto.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/PingResponseDto.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/TokenDto.java diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/DefaultGlobalSecuritySchemeApplication.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/DefaultGlobalSecuritySchemeApplication.java new file mode 100644 index 0000000000..1ce81a1e83 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/DefaultGlobalSecuritySchemeApplication.java @@ -0,0 +1,32 @@ +package com.baeldung.defaultglobalsecurityscheme; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; + +@SpringBootApplication +@OpenAPIDefinition(info = @Info(title = "Apply Default Global SecurityScheme in springdoc-openapi", version = "1.0.0"), security = { @SecurityRequirement(name = "api_key") }) +@SecurityScheme(type = SecuritySchemeType.APIKEY, name = "api_key", in = SecuritySchemeIn.HEADER) +public class DefaultGlobalSecuritySchemeApplication { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.antMatchers("/api/auth/**", "/swagger-ui-custom.html", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/swagger-ui/index.html", "/api-docs/**") + .permitAll() + .anyRequest() + .authenticated()) + .build(); + } + + public static void main(String[] args) { + SpringApplication.run(DefaultGlobalSecuritySchemeApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/controller/DefaultGlobalSecuritySchemeOpenApiController.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/controller/DefaultGlobalSecuritySchemeOpenApiController.java new file mode 100644 index 0000000000..4ad7a2a2c3 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/controller/DefaultGlobalSecuritySchemeOpenApiController.java @@ -0,0 +1,61 @@ +package com.baeldung.defaultglobalsecurityscheme.controller; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import javax.validation.Valid; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.defaultglobalsecurityscheme.dto.LoginDto; +import com.baeldung.defaultglobalsecurityscheme.dto.ApplicationExceptionDto; +import com.baeldung.defaultglobalsecurityscheme.dto.PingResponseDto; +import com.baeldung.defaultglobalsecurityscheme.dto.TokenDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirements; + +@RestController +@RequestMapping("/") +public class DefaultGlobalSecuritySchemeOpenApiController { + @RequestMapping(method = RequestMethod.POST, value = "/login", produces = { "application/json" }, consumes = { "application/json" }) + @Operation(operationId = "login", responses = { + @ApiResponse(responseCode = "200", description = "api_key to be used in the secured-ping entry point", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = TokenDto.class)) }), + @ApiResponse(responseCode = "401", description = "Unauthorized request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }) }) + @SecurityRequirements() + public ResponseEntity login(@Parameter(name = "LoginDto", description = "Login") @Valid @RequestBody(required = true) LoginDto loginDto) { + TokenDto token = new TokenDto(); + token.setRaw("Generated Token"); + return ResponseEntity.ok(token); + } + + @Operation(operationId = "ping", responses = { + @ApiResponse(responseCode = "200", description = "Ping that needs an api_key attribute in the header", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = PingResponseDto.class), examples = { @ExampleObject(value = "{ pong: '2022-06-17T18:30:33.465+02:00' }") }) }), + @ApiResponse(responseCode = "401", description = "Unauthorized request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }), + @ApiResponse(responseCode = "403", description = "Forbidden request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }) }) + @RequestMapping(method = RequestMethod.GET, value = "/ping", produces = { "application/json" }) + public ResponseEntity ping(@RequestHeader(name = "api_key", required = false) String api_key) { + int year = 2000; + int month = 1; + int dayOfMonth = 1; + int hour = 0; + int minute = 0; + int second = 0; + int nanoSeccond = 0; + ZoneOffset offset = ZoneOffset.UTC; + PingResponseDto response = new PingResponseDto(); + response.setPong(OffsetDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoSeccond, offset)); + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/ApplicationExceptionDto.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/ApplicationExceptionDto.java new file mode 100644 index 0000000000..5fb63793c4 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/ApplicationExceptionDto.java @@ -0,0 +1,26 @@ +package com.baeldung.defaultglobalsecurityscheme.dto; + +public class ApplicationExceptionDto { + private long errorCode; + private String description; + + public ApplicationExceptionDto() { + super(); + } + + public long getErrorCode() { + return errorCode; + } + + public void setErrorCode(long errorCode) { + this.errorCode = errorCode; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/LoginDto.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/LoginDto.java new file mode 100644 index 0000000000..cf88cc4d98 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/LoginDto.java @@ -0,0 +1,103 @@ +package com.baeldung.defaultglobalsecurityscheme.dto; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * LoginDto + */ + +@JsonTypeName("Login") +public class LoginDto { + + @JsonProperty("user") + private String user; + + @JsonProperty("pass") + private String pass; + + public LoginDto user(String user) { + this.user = user; + return this; + } + + /** + * Get user + * @return user + */ + + @Schema(name = "user", required = true) + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public LoginDto pass(String pass) { + this.pass = pass; + return this; + } + + /** + * Get pass + * @return pass + */ + + @Schema(name = "pass", required = true) + public String getPass() { + return pass; + } + + public void setPass(String pass) { + this.pass = pass; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LoginDto login = (LoginDto) o; + return Objects.equals(this.user, login.user) && Objects.equals(this.pass, login.pass); + } + + @Override + public int hashCode() { + return Objects.hash(user, pass); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class LoginDto {\n"); + sb.append(" user: ") + .append(toIndentedString(user)) + .append("\n"); + sb.append(" pass: ") + .append(toIndentedString(pass)) + .append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString() + .replace("\n", "\n "); + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/PingResponseDto.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/PingResponseDto.java new file mode 100644 index 0000000000..0d367785d8 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/PingResponseDto.java @@ -0,0 +1,84 @@ +package com.baeldung.defaultglobalsecurityscheme.dto; + +import java.time.OffsetDateTime; +import java.util.Objects; + +import javax.validation.Valid; + +import org.springframework.format.annotation.DateTimeFormat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * SecuredPingResponseDto + */ + +@JsonTypeName("PingResponse") +public class PingResponseDto { + + @JsonProperty("pong") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + private OffsetDateTime pong; + + public PingResponseDto pong(OffsetDateTime pong) { + this.pong = pong; + return this; + } + + /** + * Get pong + * @return pong + */ + @Valid + @Schema(name = "pong", required = false) + public OffsetDateTime getPong() { + return pong; + } + + public void setPong(OffsetDateTime pong) { + this.pong = pong; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PingResponseDto securedPingResponse = (PingResponseDto) o; + return Objects.equals(this.pong, securedPingResponse.pong); + } + + @Override + public int hashCode() { + return Objects.hash(pong); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class PingResponseDto {\n"); + sb.append(" pong: ") + .append(toIndentedString(pong)) + .append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString() + .replace("\n", "\n "); + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/TokenDto.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/TokenDto.java new file mode 100644 index 0000000000..d8f7daa09c --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/defaultglobalsecurityscheme/dto/TokenDto.java @@ -0,0 +1,57 @@ +package com.baeldung.defaultglobalsecurityscheme.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * LoginDto + */ + +@JsonTypeName("Token") +public class TokenDto { + + @JsonProperty("raw") + private String raw; + + @Schema(name = "raw", example = "app token") + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + @Override + public String toString() { + return "TokenDto [raw=" + raw + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((raw == null) ? 0 : raw.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TokenDto other = (TokenDto) obj; + if (raw == null) { + if (other.raw != null) + return false; + } else if (!raw.equals(other.raw)) + return false; + return true; + } + +} From bda1a947f7cdfa98605caf63c981db951afc90fe Mon Sep 17 00:00:00 2001 From: panagiotiskakos Date: Mon, 25 Jul 2022 15:47:51 +0300 Subject: [PATCH 15/42] [JAVA-13410] Replaced @Mappings with sequential @Mapping --- .../main/java/com/baeldung/mapper/EmployeeMapper.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mapstruct/src/main/java/com/baeldung/mapper/EmployeeMapper.java b/mapstruct/src/main/java/com/baeldung/mapper/EmployeeMapper.java index 8e00103d0e..716b05c7d9 100644 --- a/mapstruct/src/main/java/com/baeldung/mapper/EmployeeMapper.java +++ b/mapstruct/src/main/java/com/baeldung/mapper/EmployeeMapper.java @@ -6,17 +6,20 @@ import com.baeldung.entity.Division; import com.baeldung.entity.Employee; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import java.util.List; @Mapper public interface EmployeeMapper { - @Mappings({ @Mapping(target = "employeeId", source = "entity.id"), @Mapping(target = "employeeName", source = "entity.name"), @Mapping(target = "employeeStartDt", source = "entity.startDt", dateFormat = "dd-MM-yyyy HH:mm:ss") }) + @Mapping(target = "employeeId", source = "entity.id") + @Mapping(target = "employeeName", source = "entity.name") + @Mapping(target = "employeeStartDt", source = "entity.startDt", dateFormat = "dd-MM-yyyy HH:mm:ss") EmployeeDTO employeeToEmployeeDTO(Employee entity); - @Mappings({ @Mapping(target = "id", source = "dto.employeeId"), @Mapping(target = "name", source = "dto.employeeName"), @Mapping(target = "startDt", source = "dto.employeeStartDt", dateFormat = "dd-MM-yyyy HH:mm:ss") }) + @Mapping(target = "id", source = "dto.employeeId") + @Mapping(target = "name", source = "dto.employeeName") + @Mapping(target = "startDt", source = "dto.employeeStartDt", dateFormat = "dd-MM-yyyy HH:mm:ss") Employee employeeDTOtoEmployee(EmployeeDTO dto); DivisionDTO divisionToDivisionDTO(Division entity); From e12f0d064f3e7496221d16393477b6794c15fed4 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Mon, 25 Jul 2022 12:27:02 -0300 Subject: [PATCH 16/42] JAVA-13615 GitHub Issue: The code for A* is in the src/test/java directory, not the src/main/java directory --- .../{test => main}/java/com/baeldung/algorithms/astar/Graph.java | 0 .../java/com/baeldung/algorithms/astar/GraphNode.java | 0 .../java/com/baeldung/algorithms/astar/RouteFinder.java | 0 .../java/com/baeldung/algorithms/astar/RouteNode.java | 0 .../{test => main}/java/com/baeldung/algorithms/astar/Scorer.java | 0 .../baeldung/algorithms/astar/underground/HaversineScorer.java | 0 .../java/com/baeldung/algorithms/astar/underground/Station.java | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/Graph.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/GraphNode.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/RouteFinder.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/RouteNode.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/Scorer.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/underground/HaversineScorer.java (100%) rename algorithms-modules/algorithms-miscellaneous-2/src/{test => main}/java/com/baeldung/algorithms/astar/underground/Station.java (100%) diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/Graph.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/Graph.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/Graph.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/Graph.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/GraphNode.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/GraphNode.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/GraphNode.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/GraphNode.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/RouteFinder.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/RouteFinder.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/RouteFinder.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/RouteFinder.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/RouteNode.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/RouteNode.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/RouteNode.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/RouteNode.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/Scorer.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/Scorer.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/Scorer.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/Scorer.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/underground/HaversineScorer.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/underground/HaversineScorer.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/underground/HaversineScorer.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/underground/HaversineScorer.java diff --git a/algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/underground/Station.java b/algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/underground/Station.java similarity index 100% rename from algorithms-modules/algorithms-miscellaneous-2/src/test/java/com/baeldung/algorithms/astar/underground/Station.java rename to algorithms-modules/algorithms-miscellaneous-2/src/main/java/com/baeldung/algorithms/astar/underground/Station.java From 2c901e1d677623c63ed17abc9dfa8c2d26cae9c3 Mon Sep 17 00:00:00 2001 From: "thibault.faure" Date: Sat, 16 Jul 2022 00:57:06 +0200 Subject: [PATCH 17/42] BAEL-5663 code for the filter java stream to only one element article --- core-java-modules/core-java-streams-4/pom.xml | 10 +++ .../filteronlyoneelement/BenchmarkRunner.java | 56 ++++++++++++ .../filteronlyoneelement/FilterUtils.java | 50 +++++++++++ .../FilterUtilsUnitTest.java | 88 +++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/BenchmarkRunner.java create mode 100644 core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/FilterUtils.java create mode 100644 core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/filteronlyoneelement/FilterUtilsUnitTest.java diff --git a/core-java-modules/core-java-streams-4/pom.xml b/core-java-modules/core-java-streams-4/pom.xml index beed277f78..65de1d666c 100644 --- a/core-java-modules/core-java-streams-4/pom.xml +++ b/core-java-modules/core-java-streams-4/pom.xml @@ -43,6 +43,16 @@ 3.23.1 test + + org.openjdk.jmh + jmh-core + ${jmh-core.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh-generator.version} + diff --git a/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/BenchmarkRunner.java b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/BenchmarkRunner.java new file mode 100644 index 0000000000..327bdc4df3 --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/BenchmarkRunner.java @@ -0,0 +1,56 @@ +package com.baeldung.streams.filteronlyoneelement; + +import java.util.function.Predicate; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; + +public class BenchmarkRunner { + + public static void main(String[] args) throws Exception { + org.openjdk.jmh.Main.main(args); + } + + @State(Scope.Benchmark) + public static class MyState { + final Stream getIntegers() { + return IntStream.range(1, 1000000) + .boxed(); + } + + final Predicate PREDICATE = i -> i == 751879; + } + + @Benchmark + public void evaluateFindUniqueElementMatchingPredicate_WithReduction(Blackhole blackhole, MyState state) { + blackhole.consume(FilterUtils.findUniqueElementMatchingPredicate_WithReduction(state.getIntegers(), state.PREDICATE)); + } + + @Benchmark + public void evaluateFindUniqueElementMatchingPredicate_WithCollectingAndThen(Blackhole blackhole, MyState state) { + blackhole.consume(FilterUtils.findUniqueElementMatchingPredicate_WithCollectingAndThen(state.getIntegers(), state.PREDICATE)); + } + + @Benchmark + public void evaluateGetUniqueElementMatchingPredicate_WithReduction(Blackhole blackhole, MyState state) { + try { + FilterUtils.getUniqueElementMatchingPredicate_WithReduction(state.getIntegers(), state.PREDICATE); + } catch (IllegalStateException exception) { + blackhole.consume(exception); + } + } + + @Benchmark + public void evaluateGetUniqueElementMatchingPredicate_WithCollectingAndThen(Blackhole blackhole, MyState state) { + try { + FilterUtils.getUniqueElementMatchingPredicate_WithCollectingAndThen(state.getIntegers(), state.PREDICATE); + } catch (IllegalStateException exception) { + blackhole.consume(exception); + } + } + +} diff --git a/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/FilterUtils.java b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/FilterUtils.java new file mode 100644 index 0000000000..65905a7bc4 --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/filteronlyoneelement/FilterUtils.java @@ -0,0 +1,50 @@ +package com.baeldung.streams.filteronlyoneelement; + +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FilterUtils { + + public static Optional findUniqueElementMatchingPredicate_WithReduction(Stream elements, Predicate predicate) { + return elements.filter(predicate) + .collect(Collectors.reducing((a, b) -> null)); + } + + public static T getUniqueElementMatchingPredicate_WithReduction(Stream elements, Predicate predicate) { + return elements.filter(predicate) + .reduce((a, b) -> { + throw new IllegalStateException("Too many elements match the predicate"); + }) + .orElseThrow(() -> new IllegalStateException("No element matches the predicate")); + } + + public static Optional findUniqueElementMatchingPredicate_WithCollectingAndThen(Stream elements, Predicate predicate) { + return elements.filter(predicate) + .collect(Collectors.collectingAndThen(Collectors.toList(), list -> Optional.ofNullable(findUniqueElement(list)))); + } + + private static T findUniqueElement(List elements) { + if (elements.size() == 1) { + return elements.get(0); + } + return null; + } + + public static T getUniqueElementMatchingPredicate_WithCollectingAndThen(Stream elements, Predicate predicate) { + return elements.filter(predicate) + .collect(Collectors.collectingAndThen(Collectors.toList(), FilterUtils::getUniqueElement)); + } + + private static T getUniqueElement(List elements) { + if (elements.size() > 1) { + throw new IllegalStateException("Too many elements match the predicate"); + } else if (elements.size() == 0) { + throw new IllegalStateException("No element matches the predicate"); + } + return elements.get(0); + } + +} diff --git a/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/filteronlyoneelement/FilterUtilsUnitTest.java b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/filteronlyoneelement/FilterUtilsUnitTest.java new file mode 100644 index 0000000000..531dcc5abb --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/filteronlyoneelement/FilterUtilsUnitTest.java @@ -0,0 +1,88 @@ +package com.baeldung.streams.filteronlyoneelement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +public class FilterUtilsUnitTest { + + private static final Predicate IS_STRICTLY_GREATER_THAN5 = i -> i > 5; + private static final Predicate IS_STRICTLY_GREATER_THAN4 = i -> i > 4; + private static final Predicate IS_STRICTLY_GREATER_THAN3 = i -> i > 3; + + private Stream getIntegers() { + return Stream.of(1, 2, 3, 4, 5); + } + + @Test + void givenNoElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithReduction_ThenNoneFound() { + assertTrue(FilterUtils.findUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN5) + .isEmpty()); + } + + @Test + void givenTwoElementsMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithReduction_ThenEmpty() { + assertTrue(FilterUtils.findUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN3) + .isEmpty()); + } + + @Test + void givenOnlyOneElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithReduction_ThenFindsIt() { + assertEquals(5, FilterUtils.findUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN4) + .get()); + } + + @Test + void givenNoElementMatchingPredicate_WhenGetUniqueElementMatchingPredicateWithReduction_ThenThrows() { + assertThrows(IllegalStateException.class, () -> FilterUtils.getUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN5)); + } + + @Test + void givenTwoElementsMatchingPredicate_WhenGetUniqueElementMatchingPredicateWithReduction_ThenThrows() { + assertThrows(IllegalStateException.class, () -> FilterUtils.getUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN3)); + } + + @Test + void givenOnlyOneElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithReduction_ThenGetIt() { + assertEquals(5, FilterUtils.getUniqueElementMatchingPredicate_WithReduction(getIntegers(), IS_STRICTLY_GREATER_THAN4)); + } + + @Test + void givenNoElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithCollectingAndThen_ThenEmpty() { + assertTrue(FilterUtils.findUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN5) + .isEmpty()); + } + + @Test + void givenTwoElementsMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithCollectingAndThen_ThenEmpty() { + assertTrue(FilterUtils.findUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN3) + .isEmpty()); + } + + @Test + void givenOnlyOneElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithCollectingAndThen_ThenFindsIt() { + assertEquals(5, FilterUtils.findUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN4) + .get()); + } + + @Test + void givenNoElementMatchingPredicate_WhenGetUniqueElementMatchingPredicateWithCollectingAndThen_ThenThrows() { + assertThrows(IllegalStateException.class, () -> FilterUtils.getUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN5)); + } + + @Test + void givenTwoElementsMatchingPredicate_WhenGetUniqueElementMatchingPredicateWithCollectingAndThen_ThenThrows() { + assertThrows(IllegalStateException.class, () -> FilterUtils.getUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN3)); + } + + @Test + void givenOnlyOneElementMatchingPredicate_WhenFindUniqueElementMatchingPredicateWithCollectingAndThen_ThenGetIt() { + assertEquals(5, FilterUtils.getUniqueElementMatchingPredicate_WithCollectingAndThen(getIntegers(), IS_STRICTLY_GREATER_THAN4)); + } + +} From 324cb08a48bbf0e5a08a70947c7e136d236cdd28 Mon Sep 17 00:00:00 2001 From: panagiotiskakos Date: Tue, 26 Jul 2022 08:14:19 +0300 Subject: [PATCH 18/42] [JAVA-8691] --- persistence-modules/spring-data-jpa-enterprise/pom.xml | 2 +- .../src/test/resources/application-tc-jdbc.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/persistence-modules/spring-data-jpa-enterprise/pom.xml b/persistence-modules/spring-data-jpa-enterprise/pom.xml index 587261dccc..16294cd3cc 100644 --- a/persistence-modules/spring-data-jpa-enterprise/pom.xml +++ b/persistence-modules/spring-data-jpa-enterprise/pom.xml @@ -87,7 +87,7 @@ 1.3.1.Final - 1.12.2 + 1.17.3 \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/resources/application-tc-jdbc.yml b/persistence-modules/spring-data-jpa-enterprise/src/test/resources/application-tc-jdbc.yml index ad5906fa6e..d340e29bd0 100644 --- a/persistence-modules/spring-data-jpa-enterprise/src/test/resources/application-tc-jdbc.yml +++ b/persistence-modules/spring-data-jpa-enterprise/src/test/resources/application-tc-jdbc.yml @@ -1,6 +1,7 @@ spring: datasource: url: jdbc:tc:postgresql:11.1:///integration-tests-db + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver jpa: hibernate: ddl-auto: create \ No newline at end of file From 2268d19caa8bd558310e00b11a0e7ce6c4edd6f0 Mon Sep 17 00:00:00 2001 From: sampadawagde Date: Tue, 26 Jul 2022 20:01:43 +0530 Subject: [PATCH 19/42] JAVA-13134: Fix references to parents --- lightrun/api-service/pom.xml | 8 +-- lightrun/pom.xml | 7 +++ lightrun/tasks-service/pom.xml | 8 +-- lightrun/users-service/pom.xml | 8 +-- persistence-modules/fauna/pom.xml | 11 ++-- spring-cloud-modules/pom.xml | 57 ++++++++++--------- .../spring-cloud-archaius/pom.xml | 4 +- .../spring-cloud-eureka/pom.xml | 3 +- .../spring-cloud-hystrix/pom.xml | 2 +- .../echo-demo/pom.xml | 1 - .../spring-cloud-netflix-sidecar/pom.xml | 7 +-- .../sidecar-demo/pom.xml | 1 - .../spring-cloud-openfeign/pom.xml | 7 +-- .../spring-cloud-zuul/pom.xml | 4 +- .../spring-resttemplate-2/pom.xml | 2 +- 15 files changed, 68 insertions(+), 62 deletions(-) diff --git a/lightrun/api-service/pom.xml b/lightrun/api-service/pom.xml index ed9fc2152b..0541f6c5cb 100644 --- a/lightrun/api-service/pom.xml +++ b/lightrun/api-service/pom.xml @@ -10,10 +10,9 @@ Aggregator Service for LightRun Article - org.springframework.boot - spring-boot-starter-parent - 2.6.7 - + com.baelduung + lightrun + 0.0.1-SNAPSHOT @@ -43,6 +42,7 @@ 17 + 3.17.0 \ No newline at end of file diff --git a/lightrun/pom.xml b/lightrun/pom.xml index 4902688748..1fc8e5ea70 100644 --- a/lightrun/pom.xml +++ b/lightrun/pom.xml @@ -9,6 +9,13 @@ lightrun Services for LightRun Article pom + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + tasks-service diff --git a/lightrun/tasks-service/pom.xml b/lightrun/tasks-service/pom.xml index 5d4bf8b6d5..19b5f0d98e 100644 --- a/lightrun/tasks-service/pom.xml +++ b/lightrun/tasks-service/pom.xml @@ -10,10 +10,9 @@ Tasks Service for LightRun Article - org.springframework.boot - spring-boot-starter-parent - 2.6.7 - + com.baelduung + lightrun + 0.0.1-SNAPSHOT @@ -64,6 +63,7 @@ 17 + 3.17.0 \ No newline at end of file diff --git a/lightrun/users-service/pom.xml b/lightrun/users-service/pom.xml index 0be696bbf0..2db0d37037 100644 --- a/lightrun/users-service/pom.xml +++ b/lightrun/users-service/pom.xml @@ -10,10 +10,9 @@ Users Service for LightRun Article - org.springframework.boot - spring-boot-starter-parent - 2.6.7 - + com.baelduung + lightrun + 0.0.1-SNAPSHOT @@ -60,6 +59,7 @@ 17 + 3.17.0 \ No newline at end of file diff --git a/persistence-modules/fauna/pom.xml b/persistence-modules/fauna/pom.xml index 2f5da68ede..8c985e0b7c 100644 --- a/persistence-modules/fauna/pom.xml +++ b/persistence-modules/fauna/pom.xml @@ -9,11 +9,11 @@ fauna Blogging Service built with FaunaDB - - org.springframework.boot - spring-boot-starter-parent - 2.6.2 - + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 @@ -54,6 +54,7 @@ 17 + 3.17.0 \ No newline at end of file diff --git a/spring-cloud-modules/pom.xml b/spring-cloud-modules/pom.xml index 6d44cb015b..d76b3407f0 100644 --- a/spring-cloud-modules/pom.xml +++ b/spring-cloud-modules/pom.xml @@ -17,41 +17,42 @@ - spring-cloud-loadbalancer - spring-cloud-config - spring-cloud-eureka - spring-cloud-hystrix + spring-cloud-archaius + spring-cloud-aws spring-cloud-bootstrap - spring-cloud-ribbon-client - spring-cloud-zookeeper + spring-cloud-bus + spring-cloud-circuit-breaker + spring-cloud-config + spring-cloud-connectors-heroku + spring-cloud-consul + spring-cloud-contract + spring-cloud-dapr + spring-cloud-data-flow + spring-cloud-docker + spring-cloud-eureka + spring-cloud-eureka-self-preservation + spring-cloud-functions spring-cloud-gateway + spring-cloud-hystrix + spring-cloud-kubernetes + spring-cloud-loadbalancer + spring-cloud-netflix-feign + spring-cloud-netflix-sidecar + spring-cloud-openfeign + spring-cloud-open-service-broker + spring-cloud-ribbon-client + spring-cloud-ribbon-retry + spring-cloud-security + spring-cloud-sentinel + spring-cloud-sleuth spring-cloud-stream spring-cloud-stream-starters - spring-cloud-connectors-heroku - spring-cloud-aws - spring-cloud-consul - spring-cloud-zuul-eureka-integration - spring-cloud-contract - spring-cloud-kubernetes - spring-cloud-open-service-broker - spring-cloud-archaius - spring-cloud-functions - spring-cloud-vault - spring-cloud-security spring-cloud-task + spring-cloud-vault + spring-cloud-zookeeper spring-cloud-zuul + spring-cloud-zuul-eureka-integration spring-cloud-zuul-fallback - spring-cloud-ribbon-retry - spring-cloud-circuit-breaker - spring-cloud-eureka-self-preservation - spring-cloud-openfeign - spring-cloud-netflix-feign - spring-cloud-sentinel - spring-cloud-dapr - spring-cloud-docker - spring-cloud-bus - spring-cloud-data-flow - spring-cloud-sleuth diff --git a/spring-cloud-modules/spring-cloud-archaius/pom.xml b/spring-cloud-modules/spring-cloud-archaius/pom.xml index 1208daaba6..66b7bb9b19 100644 --- a/spring-cloud-modules/spring-cloud-archaius/pom.xml +++ b/spring-cloud-modules/spring-cloud-archaius/pom.xml @@ -16,11 +16,11 @@ - basic-config additional-sources-simple + basic-config + dynamodb-config extra-configs jdbc-config - dynamodb-config zookeeper-config diff --git a/spring-cloud-modules/spring-cloud-eureka/pom.xml b/spring-cloud-modules/spring-cloud-eureka/pom.xml index 2a9c4f5fde..23523f2c2f 100644 --- a/spring-cloud-modules/spring-cloud-eureka/pom.xml +++ b/spring-cloud-modules/spring-cloud-eureka/pom.xml @@ -16,10 +16,11 @@ - spring-cloud-eureka-server spring-cloud-eureka-client + spring-cloud-eureka-client-profiles spring-cloud-eureka-feign-client spring-cloud-eureka-feign-client-integration-test + spring-cloud-eureka-server diff --git a/spring-cloud-modules/spring-cloud-hystrix/pom.xml b/spring-cloud-modules/spring-cloud-hystrix/pom.xml index e1f30c25dc..41548ba36a 100644 --- a/spring-cloud-modules/spring-cloud-hystrix/pom.xml +++ b/spring-cloud-modules/spring-cloud-hystrix/pom.xml @@ -15,9 +15,9 @@ + feign-rest-consumer rest-producer rest-consumer - feign-rest-consumer \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-netflix-sidecar/echo-demo/pom.xml b/spring-cloud-modules/spring-cloud-netflix-sidecar/echo-demo/pom.xml index 60b6eab020..d8b1654eaa 100644 --- a/spring-cloud-modules/spring-cloud-netflix-sidecar/echo-demo/pom.xml +++ b/spring-cloud-modules/spring-cloud-netflix-sidecar/echo-demo/pom.xml @@ -9,7 +9,6 @@ com.baeldung.cloud spring-cloud-netflix-sidecar 0.0.1-SNAPSHOT - ../pom.xml diff --git a/spring-cloud-modules/spring-cloud-netflix-sidecar/pom.xml b/spring-cloud-modules/spring-cloud-netflix-sidecar/pom.xml index 0d95714bf9..3a72496279 100644 --- a/spring-cloud-modules/spring-cloud-netflix-sidecar/pom.xml +++ b/spring-cloud-modules/spring-cloud-netflix-sidecar/pom.xml @@ -11,10 +11,9 @@ Netflix Sidecar project for Spring Boot - com.baeldung - parent-boot-2 - 0.0.1-SNAPSHOT - ../../parent-boot-2 + com.baeldung.spring.cloud + spring-cloud-modules + 1.0.0-SNAPSHOT diff --git a/spring-cloud-modules/spring-cloud-netflix-sidecar/sidecar-demo/pom.xml b/spring-cloud-modules/spring-cloud-netflix-sidecar/sidecar-demo/pom.xml index 3c8d498456..200897652f 100644 --- a/spring-cloud-modules/spring-cloud-netflix-sidecar/sidecar-demo/pom.xml +++ b/spring-cloud-modules/spring-cloud-netflix-sidecar/sidecar-demo/pom.xml @@ -11,7 +11,6 @@ com.baeldung.cloud spring-cloud-netflix-sidecar 0.0.1-SNAPSHOT - ../pom.xml diff --git a/spring-cloud-modules/spring-cloud-openfeign/pom.xml b/spring-cloud-modules/spring-cloud-openfeign/pom.xml index 480663eb1c..5657577cb0 100644 --- a/spring-cloud-modules/spring-cloud-openfeign/pom.xml +++ b/spring-cloud-modules/spring-cloud-openfeign/pom.xml @@ -9,10 +9,9 @@ OpenFeign project for Spring Boot - com.baeldung - parent-boot-2 - 0.0.1-SNAPSHOT - ../../parent-boot-2 + com.baeldung.spring.cloud + spring-cloud-modules + 1.0.0-SNAPSHOT diff --git a/spring-cloud-modules/spring-cloud-zuul/pom.xml b/spring-cloud-modules/spring-cloud-zuul/pom.xml index e99cbdf355..b04a21da1c 100644 --- a/spring-cloud-modules/spring-cloud-zuul/pom.xml +++ b/spring-cloud-modules/spring-cloud-zuul/pom.xml @@ -17,9 +17,9 @@ spring-zuul-foos-resource - spring-zuul-ui - spring-zuul-rate-limiting spring-zuul-post-filter + spring-zuul-rate-limiting + spring-zuul-ui diff --git a/spring-web-modules/spring-resttemplate-2/pom.xml b/spring-web-modules/spring-resttemplate-2/pom.xml index b87b245da9..0228b1dc4f 100644 --- a/spring-web-modules/spring-resttemplate-2/pom.xml +++ b/spring-web-modules/spring-resttemplate-2/pom.xml @@ -12,7 +12,7 @@ com.baeldung parent-boot-2 0.0.1-SNAPSHOT - ../../parent-boot-2/pom.xml + ../../parent-boot-2 From d083a20aa92d1064416009512e2c99a561c4e14c Mon Sep 17 00:00:00 2001 From: Graham Cox Date: Tue, 26 Jul 2022 20:11:45 +0100 Subject: [PATCH 20/42] BAEL-5686: Implementing a Map with Multiple Keys (#12531) --- .../map/multikey/BaseClassUserCache.java | 24 +++++++ .../map/multikey/MultipleMapsUserCache.java | 25 ++++++++ .../java/com/baeldung/map/multikey/User.java | 13 ++++ .../map/multikey/WrapperClassUserCache.java | 42 +++++++++++++ .../multikey/WrapperInterfaceUserCache.java | 62 +++++++++++++++++++ .../multikey/BaseClassUserCacheUnitTest.java | 32 ++++++++++ .../MultipleMapsUserCacheUnitTest.java | 33 ++++++++++ .../WrapperClassUserCacheUnitTest.java | 33 ++++++++++ .../WrapperInterfaceUserCacheUnitTest.java | 33 ++++++++++ 9 files changed, 297 insertions(+) create mode 100644 core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/BaseClassUserCache.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/MultipleMapsUserCache.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/User.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperClassUserCache.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperInterfaceUserCache.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/BaseClassUserCacheUnitTest.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/MultipleMapsUserCacheUnitTest.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperClassUserCacheUnitTest.java create mode 100644 core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperInterfaceUserCacheUnitTest.java diff --git a/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/BaseClassUserCache.java b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/BaseClassUserCache.java new file mode 100644 index 0000000000..1449727efc --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/BaseClassUserCache.java @@ -0,0 +1,24 @@ +package com.baeldung.map.multikey; + +import java.util.HashMap; +import java.util.Map; + +public class BaseClassUserCache { + private final Map cache = new HashMap<>(); + + public User getById(String id) { + return cache.get(id); + } + + public User getById(Long id) { + return cache.get(id); + } + + public void storeById(String id, User user) { + cache.put(id, user); + } + + public void storeById(Long id, User user) { + cache.put(id, user); + } +} diff --git a/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/MultipleMapsUserCache.java b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/MultipleMapsUserCache.java new file mode 100644 index 0000000000..6b7e56f209 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/MultipleMapsUserCache.java @@ -0,0 +1,25 @@ +package com.baeldung.map.multikey; + +import java.util.HashMap; +import java.util.Map; + +public class MultipleMapsUserCache { + private final Map stringCache = new HashMap<>(); + private final Map longCache = new HashMap<>(); + + public User getById(String id) { + return stringCache.get(id); + } + + public User getById(Long id) { + return longCache.get(id); + } + + public void storeById(String id, User user) { + stringCache.put(id, user); + } + + public void storeById(Long id, User user) { + longCache.put(id, user); + } +} diff --git a/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/User.java b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/User.java new file mode 100644 index 0000000000..392587d305 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/User.java @@ -0,0 +1,13 @@ +package com.baeldung.map.multikey; + +public class User { + private final String name; + + public User(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperClassUserCache.java b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperClassUserCache.java new file mode 100644 index 0000000000..3ce920ead1 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperClassUserCache.java @@ -0,0 +1,42 @@ +package com.baeldung.map.multikey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class WrapperClassUserCache { + private Map cache = new HashMap<>(); + + public User getById(CacheKey key) { + return cache.get(key); + } + + public void storeById(CacheKey key, User user) { + cache.put(key, user); + } + + public static class CacheKey { + private final Object value; + + public CacheKey(String value) { + this.value = value; + } + + public CacheKey(Long value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CacheKey cacheKey = (CacheKey) o; + return value.equals(cacheKey.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } +} diff --git a/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperInterfaceUserCache.java b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperInterfaceUserCache.java new file mode 100644 index 0000000000..586a1bdee7 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/main/java/com/baeldung/map/multikey/WrapperInterfaceUserCache.java @@ -0,0 +1,62 @@ +package com.baeldung.map.multikey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class WrapperInterfaceUserCache { + private Map cache = new HashMap<>(); + + public User getById(CacheKey key) { + return cache.get(key); + } + + public void storeById(CacheKey key, User user) { + cache.put(key, user); + } + + public interface CacheKey { + } + + public static class StringCacheKey implements CacheKey{ + private final String value; + + public StringCacheKey(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringCacheKey that = (StringCacheKey) o; + return value.equals(that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + + public static class LongCacheKey implements CacheKey { + private final Long value; + + public LongCacheKey(Long value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LongCacheKey that = (LongCacheKey) o; + return value.equals(that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } +} diff --git a/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/BaseClassUserCacheUnitTest.java b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/BaseClassUserCacheUnitTest.java new file mode 100644 index 0000000000..6a35e4c10a --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/BaseClassUserCacheUnitTest.java @@ -0,0 +1,32 @@ +package com.baeldung.map.multikey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BaseClassUserCacheUnitTest { + private BaseClassUserCache cache = new BaseClassUserCache(); + + @BeforeEach + public void setup() { + cache.storeById("a", new User("User A")); + cache.storeById("b", new User("User B")); + cache.storeById(3L, new User("User 3")); + cache.storeById(4L, new User("User 4")); + } + + @Test + public void getByString() { + User user = cache.getById("b"); + assertNotNull(user); + assertEquals("User B", user.getName()); + } + + @Test + public void getByLong() { + User user = cache.getById(4L); + assertNotNull(user); + assertEquals("User 4", user.getName()); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/MultipleMapsUserCacheUnitTest.java b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/MultipleMapsUserCacheUnitTest.java new file mode 100644 index 0000000000..0b8473c0df --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/MultipleMapsUserCacheUnitTest.java @@ -0,0 +1,33 @@ +package com.baeldung.map.multikey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class MultipleMapsUserCacheUnitTest { + private MultipleMapsUserCache cache = new MultipleMapsUserCache(); + + @BeforeEach + public void setup() { + cache.storeById("a", new User("User A")); + cache.storeById("b", new User("User B")); + cache.storeById(3L, new User("User 3")); + cache.storeById(4L, new User("User 4")); + } + + @Test + public void getByString() { + User user = cache.getById("b"); + assertNotNull(user); + assertEquals("User B", user.getName()); + } + + @Test + public void getByLong() { + User user = cache.getById(4L); + assertNotNull(user); + assertEquals("User 4", user.getName()); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperClassUserCacheUnitTest.java b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperClassUserCacheUnitTest.java new file mode 100644 index 0000000000..caff7ef050 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperClassUserCacheUnitTest.java @@ -0,0 +1,33 @@ +package com.baeldung.map.multikey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class WrapperClassUserCacheUnitTest { + private WrapperClassUserCache cache = new WrapperClassUserCache(); + + @BeforeEach + public void setup() { + cache.storeById(new WrapperClassUserCache.CacheKey("a"), new User("User A")); + cache.storeById(new WrapperClassUserCache.CacheKey("b"), new User("User B")); + cache.storeById(new WrapperClassUserCache.CacheKey(3L), new User("User 3")); + cache.storeById(new WrapperClassUserCache.CacheKey(4L), new User("User 4")); + } + + @Test + public void getByString() { + User user = cache.getById(new WrapperClassUserCache.CacheKey("b")); + assertNotNull(user); + assertEquals("User B", user.getName()); + } + + @Test + public void getByLong() { + User user = cache.getById(new WrapperClassUserCache.CacheKey(4L)); + assertNotNull(user); + assertEquals("User 4", user.getName()); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperInterfaceUserCacheUnitTest.java b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperInterfaceUserCacheUnitTest.java new file mode 100644 index 0000000000..211e6aa182 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-5/src/test/java/com/baeldung/map/multikey/WrapperInterfaceUserCacheUnitTest.java @@ -0,0 +1,33 @@ +package com.baeldung.map.multikey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class WrapperInterfaceUserCacheUnitTest { + private WrapperInterfaceUserCache cache = new WrapperInterfaceUserCache(); + + @BeforeEach + public void setup() { + cache.storeById(new WrapperInterfaceUserCache.StringCacheKey("a"), new User("User A")); + cache.storeById(new WrapperInterfaceUserCache.StringCacheKey("b"), new User("User B")); + cache.storeById(new WrapperInterfaceUserCache.LongCacheKey(3L), new User("User 3")); + cache.storeById(new WrapperInterfaceUserCache.LongCacheKey(4L), new User("User 4")); + } + + @Test + public void getByString() { + User user = cache.getById(new WrapperInterfaceUserCache.StringCacheKey("b")); + assertNotNull(user); + assertEquals("User B", user.getName()); + } + + @Test + public void getByLong() { + User user = cache.getById(new WrapperInterfaceUserCache.LongCacheKey(4L)); + assertNotNull(user); + assertEquals("User 4", user.getName()); + } +} \ No newline at end of file From ba18865ff3aa6a2906c5188e0e72581e41348486 Mon Sep 17 00:00:00 2001 From: Tapan Avasthi Date: Wed, 27 Jul 2022 05:51:31 +0530 Subject: [PATCH 21/42] BAEL-4840: Read Flux into a single InputStream (#12500) Co-authored-by: Tapan Avasthi --- .../databuffer/DataBufferToInputStream.java | 94 +++++++++++++++++++ .../DataBufferToInputStreamUnitTest.java | 77 +++++++++++++++ .../src/test/resources/user-response.json | 72 ++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 spring-5-reactive-modules/spring-5-reactive-3/src/main/java/com/baeldung/databuffer/DataBufferToInputStream.java create mode 100644 spring-5-reactive-modules/spring-5-reactive-3/src/test/java/databuffer/DataBufferToInputStreamUnitTest.java create mode 100644 spring-5-reactive-modules/spring-5-reactive-3/src/test/resources/user-response.json diff --git a/spring-5-reactive-modules/spring-5-reactive-3/src/main/java/com/baeldung/databuffer/DataBufferToInputStream.java b/spring-5-reactive-modules/spring-5-reactive-3/src/main/java/com/baeldung/databuffer/DataBufferToInputStream.java new file mode 100644 index 0000000000..82f0658f51 --- /dev/null +++ b/spring-5-reactive-modules/spring-5-reactive-3/src/main/java/com/baeldung/databuffer/DataBufferToInputStream.java @@ -0,0 +1,94 @@ +package com.baeldung.databuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.web.reactive.function.BodyExtractors; +import org.springframework.web.reactive.function.client.WebClient; + +import reactor.core.publisher.Flux; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +public class DataBufferToInputStream { + private static final Logger logger = LoggerFactory.getLogger(DataBufferToInputStream.class); + private static final String REQUEST_ENDPOINT = "https://gorest.co.in/public/v2/users"; + + private static WebClient getWebClient() { + WebClient.Builder webClientBuilder = WebClient.builder(); + return webClientBuilder.build(); + } + + public static InputStream getResponseAsInputStream(WebClient client, String url) throws IOException, InterruptedException { + + PipedOutputStream pipedOutputStream = new PipedOutputStream(); + PipedInputStream pipedInputStream = new PipedInputStream(1024 * 10); + pipedInputStream.connect(pipedOutputStream); + + Flux body = client.get() + .uri(url) + .exchangeToFlux(clientResponse -> { + return clientResponse.body(BodyExtractors.toDataBuffers()); + }) + .doOnError(error -> { + logger.error("error occurred while reading body", error); + }) + .doFinally(s -> { + try { + pipedOutputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .doOnCancel(() -> { + logger.error("Get request is cancelled"); + }); + + DataBufferUtils.write(body, pipedOutputStream) + .log("Writing to output buffer") + .subscribe(); + return pipedInputStream; + } + + private static String readContentFromPipedInputStream(PipedInputStream stream) throws IOException { + StringBuffer contentStringBuffer = new StringBuffer(); + try { + Thread pipeReader = new Thread(() -> { + try { + contentStringBuffer.append(readContent(stream)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + pipeReader.start(); + pipeReader.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + stream.close(); + } + + return String.valueOf(contentStringBuffer); + } + + private static String readContent(InputStream stream) throws IOException { + StringBuffer contentStringBuffer = new StringBuffer(); + byte[] tmp = new byte[stream.available()]; + int byteCount = stream.read(tmp, 0, tmp.length); + logger.info(String.format("read %d bytes from the stream\n", byteCount)); + contentStringBuffer.append(new String(tmp)); + return String.valueOf(contentStringBuffer); + } + + public static void main(String[] args) throws IOException, InterruptedException { + WebClient webClient = getWebClient(); + InputStream inputStream = getResponseAsInputStream(webClient, REQUEST_ENDPOINT); + Thread.sleep(3000); + String content = readContentFromPipedInputStream((PipedInputStream) inputStream); + logger.info("response content: \n{}", content.replace("}", "}\n")); + } +} diff --git a/spring-5-reactive-modules/spring-5-reactive-3/src/test/java/databuffer/DataBufferToInputStreamUnitTest.java b/spring-5-reactive-modules/spring-5-reactive-3/src/test/java/databuffer/DataBufferToInputStreamUnitTest.java new file mode 100644 index 0000000000..b885919bbb --- /dev/null +++ b/spring-5-reactive-modules/spring-5-reactive-3/src/test/java/databuffer/DataBufferToInputStreamUnitTest.java @@ -0,0 +1,77 @@ +package databuffer; + +import com.baeldung.databuffer.DataBufferToInputStream; + +import io.restassured.internal.util.IOUtils; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClient; + +import reactor.core.publisher.Mono; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class DataBufferToInputStreamUnitTest { + private String getResponseStub() throws IOException { + InputStream inputStream = null; + BufferedReader reader = null; + String content = null; + try { + inputStream = this.getClass() + .getClassLoader() + .getResourceAsStream("user-response.json"); + if (inputStream != null) { + reader = new BufferedReader(new InputStreamReader(inputStream)); + content = reader.lines() + .collect(Collectors.joining(System.lineSeparator())); + } + } catch (Exception ex) { + throw new RuntimeException("exception caught while getting response stub"); + } finally { + reader.close(); + inputStream.close(); + } + return content; + } + + private InputStream getResponseStubAsInputStream() { + return this.getClass() + .getClassLoader() + .getResourceAsStream("user-response.json"); + } + + private WebClient getMockWebClient() throws IOException { + String content = getResponseStub(); + ClientResponse clientResponse = ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "application/json") + .body(content) + .build(); + + ExchangeFunction exchangeFunction = clientRequest -> Mono.just(clientResponse); + + WebClient.Builder webClientBuilder = WebClient.builder() + .exchangeFunction(exchangeFunction); + WebClient webClient = webClientBuilder.build(); + return webClient; + } + + @Test + public void testResponseAsInputStream() throws IOException, InterruptedException { + String mockUrl = Mockito.anyString(); + WebClient mockWebClient = getMockWebClient(); + InputStream inputStream = DataBufferToInputStream.getResponseAsInputStream(mockWebClient, mockUrl); + byte[] expectedBytes = IOUtils.toByteArray(getResponseStubAsInputStream()); + byte[] actualBytes = IOUtils.toByteArray(inputStream); + assertArrayEquals(expectedBytes, actualBytes); + } +} \ No newline at end of file diff --git a/spring-5-reactive-modules/spring-5-reactive-3/src/test/resources/user-response.json b/spring-5-reactive-modules/spring-5-reactive-3/src/test/resources/user-response.json new file mode 100644 index 0000000000..33c03dc492 --- /dev/null +++ b/spring-5-reactive-modules/spring-5-reactive-3/src/test/resources/user-response.json @@ -0,0 +1,72 @@ +[ + { + "id": 2683, + "name": "Maheswar Kocchar", + "email": "maheswar_kocchar@kihn.info", + "gender": "male", + "status": "active" + }, + { + "id": 2680, + "name": "Lakshminath Khan", + "email": "lakshminath_khan@barrows-cormier.biz", + "gender": "female", + "status": "inactive" + }, + { + "id": 2679, + "name": "Tarun Arora", + "email": "tarun_arora@rolfson.net", + "gender": "female", + "status": "inactive" + }, + { + "id": 2678, + "name": "Agnivesh Dubashi", + "email": "dubashi_agnivesh@senger.name", + "gender": "male", + "status": "inactive" + }, + { + "id": 2677, + "name": "Dhanu Gowda", + "email": "gowda_dhanu@hayes.org", + "gender": "male", + "status": "active" + }, + { + "id": 2675, + "name": "Harinakshi Pilla Jr.", + "email": "pilla_jr_harinakshi@rutherford-monahan.com", + "gender": "female", + "status": "inactive" + }, + { + "id": 2673, + "name": "Kalpana Prajapat", + "email": "prajapat_kalpana@wilkinson-schaefer.net", + "gender": "female", + "status": "active" + }, + { + "id": 2672, + "name": "Chakradhar Jha", + "email": "jha_chakradhar@baumbach.info", + "gender": "male", + "status": "active" + }, + { + "id": 2670, + "name": "Divaakar Deshpande Jr.", + "email": "deshpande_jr_divaakar@mertz.info", + "gender": "female", + "status": "inactive" + }, + { + "id": 2669, + "name": "Prasanna Mehra", + "email": "prasanna_mehra@ruecker-larkin.name", + "gender": "female", + "status": "active" + } +] \ No newline at end of file From b055a790be1c4b54efad2c1baa06773791fbc9ef Mon Sep 17 00:00:00 2001 From: AttilaUhrin Date: Wed, 27 Jul 2022 06:00:18 +0200 Subject: [PATCH 22/42] Add Spring JMS testing examples. (#12518) --- spring-jms/pom.xml | 19 ++++ .../spring/jms/testing/JmsApplication.java | 13 +++ .../spring/jms/testing/JmsConfig.java | 34 ++++++ .../spring/jms/testing/MessageListener.java | 20 ++++ .../spring/jms/testing/MessageSender.java | 21 ++++ .../EmbeddedActiveMqIntegrationTest.java | 91 ++++++++++++++++ ...TestContainersActiveMqIntegrationTest.java | 101 ++++++++++++++++++ 7 files changed, 299 insertions(+) create mode 100644 spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsApplication.java create mode 100644 spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsConfig.java create mode 100644 spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageListener.java create mode 100644 spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageSender.java create mode 100644 spring-jms/src/test/java/com/baeldung/spring/jms/testing/EmbeddedActiveMqIntegrationTest.java create mode 100644 spring-jms/src/test/java/com/baeldung/spring/jms/testing/TestContainersActiveMqIntegrationTest.java diff --git a/spring-jms/pom.xml b/spring-jms/pom.xml index 09bf854221..ab202402f3 100644 --- a/spring-jms/pom.xml +++ b/spring-jms/pom.xml @@ -41,6 +41,25 @@ ${spring-boot-test.version} test + + + org.mockito + mockito-core + 4.6.1 + test + + + org.apache.activemq.tooling + activemq-junit + 5.16.5 + test + + + org.testcontainers + testcontainers + 1.17.3 + test + diff --git a/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsApplication.java b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsApplication.java new file mode 100644 index 0000000000..0a89d422b4 --- /dev/null +++ b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.spring.jms.testing; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; + +@ComponentScan +public class JmsApplication { + + public static void main(String[] args) { + ApplicationContext context = new AnnotationConfigApplicationContext(JmsApplication.class); + } +} diff --git a/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsConfig.java b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsConfig.java new file mode 100644 index 0000000000..92abb32861 --- /dev/null +++ b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/JmsConfig.java @@ -0,0 +1,34 @@ +package com.baeldung.spring.jms.testing; + +import javax.jms.ConnectionFactory; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; +import org.springframework.jms.core.JmsTemplate; + +@Configuration +@EnableJms +public class JmsConfig { + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory() { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + return factory; + } + + @Bean + public ConnectionFactory connectionFactory() { + return new ActiveMQConnectionFactory("tcp://localhost:61616"); + } + + @Bean + public JmsTemplate jmsTemplate() { + return new JmsTemplate(connectionFactory()); + } + +} diff --git a/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageListener.java b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageListener.java new file mode 100644 index 0000000000..f66a99c876 --- /dev/null +++ b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageListener.java @@ -0,0 +1,20 @@ +package com.baeldung.spring.jms.testing; + +import javax.jms.JMSException; +import javax.jms.TextMessage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +@Component +public class MessageListener { + + private static final Logger logger = LoggerFactory.getLogger(MessageListener.class); + + @JmsListener(destination = "queue-1") + public void sampleJmsListenerMethod(TextMessage message) throws JMSException { + logger.info("JMS listener received text message: {}", message.getText()); + } +} diff --git a/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageSender.java b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageSender.java new file mode 100644 index 0000000000..6cb199b0e9 --- /dev/null +++ b/spring-jms/src/main/java/com/baeldung/spring/jms/testing/MessageSender.java @@ -0,0 +1,21 @@ +package com.baeldung.spring.jms.testing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.stereotype.Component; + +@Component +public class MessageSender { + + @Autowired + private JmsTemplate jmsTemplate; + + private static final Logger logger = LoggerFactory.getLogger(MessageSender.class); + + public void sendTextMessage(String destination, String message) { + logger.info("Sending message to {} destination with text {}", destination, message); + jmsTemplate.send(destination, s -> s.createTextMessage(message)); + } +} diff --git a/spring-jms/src/test/java/com/baeldung/spring/jms/testing/EmbeddedActiveMqIntegrationTest.java b/spring-jms/src/test/java/com/baeldung/spring/jms/testing/EmbeddedActiveMqIntegrationTest.java new file mode 100644 index 0000000000..6644ee50ac --- /dev/null +++ b/spring-jms/src/test/java/com/baeldung/spring/jms/testing/EmbeddedActiveMqIntegrationTest.java @@ -0,0 +1,91 @@ +package com.baeldung.spring.jms.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.TextMessage; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.junit.EmbeddedActiveMQBroker; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.spring.jms.testing.EmbeddedActiveMqIntegrationTest.TestConfiguration; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { TestConfiguration.class }) +public class EmbeddedActiveMqIntegrationTest { + + @ClassRule + public static EmbeddedActiveMQBroker embeddedBroker = new EmbeddedActiveMQBroker(); + + @SpyBean + private MessageListener messageListener; + + @SpyBean + private MessageSender messageSender; + + @Test + public void whenListening_thenReceivingCorrectMessage() throws JMSException { + String queueName = "queue-1"; + String messageText = "Test message"; + + embeddedBroker.pushMessage(queueName, messageText); + assertEquals(1, embeddedBroker.getMessageCount(queueName)); + + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(TextMessage.class); + + Mockito.verify(messageListener, Mockito.timeout(100)) + .sampleJmsListenerMethod(messageCaptor.capture()); + + TextMessage receivedMessage = messageCaptor.getValue(); + assertEquals(messageText, receivedMessage.getText()); + } + + @Test + public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException { + String queueName = "queue-2"; + String messageText = "Test message"; + + messageSender.sendTextMessage(queueName, messageText); + + assertEquals(1, embeddedBroker.getMessageCount(queueName)); + TextMessage sentMessage = embeddedBroker.peekTextMessage(queueName); + assertEquals(messageText, sentMessage.getText()); + } + + @Configuration + @EnableJms + static class TestConfiguration { + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory() { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + return factory; + } + + @Bean + public ConnectionFactory connectionFactory() { + return new ActiveMQConnectionFactory(embeddedBroker.getVmURL()); + } + + @Bean + public JmsTemplate jmsTemplate() { + return new JmsTemplate(connectionFactory()); + } + } + +} diff --git a/spring-jms/src/test/java/com/baeldung/spring/jms/testing/TestContainersActiveMqIntegrationTest.java b/spring-jms/src/test/java/com/baeldung/spring/jms/testing/TestContainersActiveMqIntegrationTest.java new file mode 100644 index 0000000000..d117b90423 --- /dev/null +++ b/spring-jms/src/test/java/com/baeldung/spring/jms/testing/TestContainersActiveMqIntegrationTest.java @@ -0,0 +1,101 @@ +package com.baeldung.spring.jms.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.TextMessage; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.assertj.core.api.Assertions; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +import com.baeldung.spring.jms.testing.TestContainersActiveMqIntegrationTest.TestConfiguration; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { TestConfiguration.class, MessageSender.class }) +public class TestContainersActiveMqIntegrationTest { + + @ClassRule + public static GenericContainer activeMqContainer = new GenericContainer<>(DockerImageName.parse("rmohr/activemq:5.14.3")).withExposedPorts(61616); + + @SpyBean + private MessageListener messageListener; + + @Autowired + private MessageSender messageSender; + + @Autowired + private JmsTemplate jmsTemplate; + + @Test + public void whenListening_thenReceivingCorrectMessage() throws JMSException { + String queueName = "queue-1"; + String messageText = "Test message"; + + jmsTemplate.send(queueName, s -> s.createTextMessage(messageText)); + + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(TextMessage.class); + + Mockito.verify(messageListener, Mockito.timeout(100)) + .sampleJmsListenerMethod(messageCaptor.capture()); + + TextMessage receivedMessage = messageCaptor.getValue(); + assertEquals(messageText, receivedMessage.getText()); + } + + @Test + public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException { + String queueName = "queue-2"; + String messageText = "Test message"; + + messageSender.sendTextMessage(queueName, messageText); + + Message sentMessage = jmsTemplate.receive(queueName); + Assertions.assertThat(sentMessage) + .isInstanceOf(TextMessage.class); + + assertEquals(messageText, ((TextMessage) sentMessage).getText()); + } + + @Configuration + @EnableJms + static class TestConfiguration { + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory() { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + return factory; + } + + @Bean + public ConnectionFactory connectionFactory() { + String brokerUrlFormat = "tcp://%s:%d"; + String brokerUrl = String.format(brokerUrlFormat, activeMqContainer.getHost(), activeMqContainer.getFirstMappedPort()); + return new ActiveMQConnectionFactory(brokerUrl); + } + + @Bean + public JmsTemplate jmsTemplate() { + return new JmsTemplate(connectionFactory()); + } + } + +} From ad61eaa45a5e643cecb1ddcfed5066029033c7f4 Mon Sep 17 00:00:00 2001 From: sampadawagde Date: Wed, 27 Jul 2022 10:23:01 +0530 Subject: [PATCH 23/42] JAVA-13134 --- spring-cloud-modules/pom.xml | 59 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/spring-cloud-modules/pom.xml b/spring-cloud-modules/pom.xml index d76b3407f0..6d44cb015b 100644 --- a/spring-cloud-modules/pom.xml +++ b/spring-cloud-modules/pom.xml @@ -17,42 +17,41 @@ - spring-cloud-archaius - spring-cloud-aws - spring-cloud-bootstrap - spring-cloud-bus - spring-cloud-circuit-breaker - spring-cloud-config - spring-cloud-connectors-heroku - spring-cloud-consul - spring-cloud-contract - spring-cloud-dapr - spring-cloud-data-flow - spring-cloud-docker - spring-cloud-eureka - spring-cloud-eureka-self-preservation - spring-cloud-functions - spring-cloud-gateway - spring-cloud-hystrix - spring-cloud-kubernetes spring-cloud-loadbalancer - spring-cloud-netflix-feign - spring-cloud-netflix-sidecar - spring-cloud-openfeign - spring-cloud-open-service-broker + spring-cloud-config + spring-cloud-eureka + spring-cloud-hystrix + spring-cloud-bootstrap spring-cloud-ribbon-client - spring-cloud-ribbon-retry - spring-cloud-security - spring-cloud-sentinel - spring-cloud-sleuth + spring-cloud-zookeeper + spring-cloud-gateway spring-cloud-stream spring-cloud-stream-starters - spring-cloud-task - spring-cloud-vault - spring-cloud-zookeeper - spring-cloud-zuul + spring-cloud-connectors-heroku + spring-cloud-aws + spring-cloud-consul spring-cloud-zuul-eureka-integration + spring-cloud-contract + spring-cloud-kubernetes + spring-cloud-open-service-broker + spring-cloud-archaius + spring-cloud-functions + spring-cloud-vault + spring-cloud-security + spring-cloud-task + spring-cloud-zuul spring-cloud-zuul-fallback + spring-cloud-ribbon-retry + spring-cloud-circuit-breaker + spring-cloud-eureka-self-preservation + spring-cloud-openfeign + spring-cloud-netflix-feign + spring-cloud-sentinel + spring-cloud-dapr + spring-cloud-docker + spring-cloud-bus + spring-cloud-data-flow + spring-cloud-sleuth From cf4225a9eaae0d2e6439236b5c7f35bb854bea8e Mon Sep 17 00:00:00 2001 From: Avin Buricha Date: Wed, 27 Jul 2022 20:01:18 +0530 Subject: [PATCH 24/42] Moved article code to new module (#12423) --- core-java-modules/core-java-11-2/README.md | 3 +- core-java-modules/core-java-11-3/README.md | 6 +++ core-java-modules/core-java-11-3/pom.xml | 37 +++++++++++++++++++ .../HttpClientParametersLiveTest.java | 0 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 core-java-modules/core-java-11-3/README.md create mode 100644 core-java-modules/core-java-11-3/pom.xml rename core-java-modules/{core-java-11-2 => core-java-11-3}/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java (100%) diff --git a/core-java-modules/core-java-11-2/README.md b/core-java-modules/core-java-11-2/README.md index b864e1ca99..62acd33188 100644 --- a/core-java-modules/core-java-11-2/README.md +++ b/core-java-modules/core-java-11-2/README.md @@ -12,5 +12,4 @@ This module contains articles about Java 11 core features - [Java HTTPS Client Certificate Authentication](https://www.baeldung.com/java-https-client-certificate-authentication) - [Call Methods at Runtime Using Java Reflection](https://www.baeldung.com/java-method-reflection) - [Java HttpClient Basic Authentication](https://www.baeldung.com/java-httpclient-basic-auth) -- [Java HttpClient With SSL](https://www.baeldung.com/java-httpclient-ssl) -- [Adding Parameters to Java HttpClient Requests](https://www.baeldung.com/java-httpclient-request-parameters) +- [Java HttpClient With SSL](https://www.baeldung.com/java-httpclient-ssl) \ No newline at end of file diff --git a/core-java-modules/core-java-11-3/README.md b/core-java-modules/core-java-11-3/README.md new file mode 100644 index 0000000000..f46ab02bca --- /dev/null +++ b/core-java-modules/core-java-11-3/README.md @@ -0,0 +1,6 @@ +## Core Java 11 + +This module contains articles about Java 11 core features + +### Relevant articles +- [Adding Parameters to Java HttpClient Requests](https://www.baeldung.com/java-httpclient-request-parameters) diff --git a/core-java-modules/core-java-11-3/pom.xml b/core-java-modules/core-java-11-3/pom.xml new file mode 100644 index 0000000000..b4a4591b28 --- /dev/null +++ b/core-java-modules/core-java-11-3/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + core-java-11-3 + 0.1.0-SNAPSHOT + core-java-11-3 + jar + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + ../../pom.xml + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source.version} + ${maven.compiler.target.version} + + + + + + + 11 + 11 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java b/core-java-modules/core-java-11-3/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java similarity index 100% rename from core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java rename to core-java-modules/core-java-11-3/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java From 1eeb2721e7c378a3f6040dd2e8a58a5dc9644ede Mon Sep 17 00:00:00 2001 From: Christian GERMAN Date: Thu, 28 Jul 2022 14:30:02 +0200 Subject: [PATCH 25/42] BAEL-5589 - Skipping tests with docker from pipeline. --- .../{IntegrationTest.java => KeycloakTestContainers.java} | 6 +++--- ...erIntegrationTest.java => UserControllerManualTest.java} | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) rename spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/{IntegrationTest.java => KeycloakTestContainers.java} (95%) rename spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/{UserControllerIntegrationTest.java => UserControllerManualTest.java} (77%) diff --git a/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/IntegrationTest.java b/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/KeycloakTestContainers.java similarity index 95% rename from spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/IntegrationTest.java rename to spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/KeycloakTestContainers.java index 902c27b16e..44e24c98d1 100644 --- a/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/IntegrationTest.java +++ b/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/KeycloakTestContainers.java @@ -26,11 +26,11 @@ import org.springframework.web.reactive.function.client.WebClient; import dasniko.testcontainers.keycloak.KeycloakContainer; import io.restassured.RestAssured; -@ContextConfiguration(initializers = { IntegrationTest.Initializer.class }) +@ContextConfiguration(initializers = { KeycloakTestContainers.Initializer.class }) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public abstract class IntegrationTest { +public abstract class KeycloakTestContainers { - private static final Logger LOGGER = LoggerFactory.getLogger(IntegrationTest.class.getName()); + private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakTestContainers.class.getName()); @LocalServerPort private int port; diff --git a/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerIntegrationTest.java b/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerManualTest.java similarity index 77% rename from spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerIntegrationTest.java rename to spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerManualTest.java index 4015612860..817c8ae130 100644 --- a/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerIntegrationTest.java +++ b/spring-boot-modules/spring-boot-testing-2/src/test/java/com/baeldung/keycloaktestcontainers/UserControllerManualTest.java @@ -5,7 +5,11 @@ import static org.hamcrest.Matchers.equalTo; import org.junit.jupiter.api.Test; -class UserControllerIntegrationTest extends IntegrationTest { +/** + * Requires Docker running on the machine to run without errors + * Therefore, skipped from pipeline + */ +class UserControllerManualTest extends KeycloakTestContainers { @Test void givenAuthenticatedUser_whenGetMe_shouldReturnMyInfo() { From b358b17bc00c33d7eeabc0718f37ea90ce5802fa Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Thu, 28 Jul 2022 23:15:33 +0500 Subject: [PATCH 26/42] Updated README.md added link back to the articles: https://www.baeldung.com/java-string-remove-whitespace --- core-java-modules/core-java-string-operations-4/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/core-java-modules/core-java-string-operations-4/README.md b/core-java-modules/core-java-string-operations-4/README.md index ac52ee4ab5..07af6d23c9 100644 --- a/core-java-modules/core-java-string-operations-4/README.md +++ b/core-java-modules/core-java-string-operations-4/README.md @@ -8,3 +8,4 @@ - [Check if a String Ends with a Certain Pattern in Java](https://www.baeldung.com/java-string-ends-pattern) - [Check if a Character is a Vowel in Java](https://www.baeldung.com/java-check-character-vowel) - [How to Truncate a String in Java](https://www.baeldung.com/java-truncating-strings) +- [Remove Whitespace From a String in Java](https://www.baeldung.com/java-string-remove-whitespace) From 47aaa04661679931b6e8a9a5754a5a875b622213 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Thu, 28 Jul 2022 23:25:23 +0500 Subject: [PATCH 27/42] Updated README.md added link back to the article: https://www.baeldung.com/spring-openapi-global-securityscheme --- spring-boot-modules/spring-boot-springdoc/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-modules/spring-boot-springdoc/README.md b/spring-boot-modules/spring-boot-springdoc/README.md index 733e31e698..3d0fd19ab1 100644 --- a/spring-boot-modules/spring-boot-springdoc/README.md +++ b/spring-boot-modules/spring-boot-springdoc/README.md @@ -6,3 +6,4 @@ - [Swagger @Api Description Is Deprecated](https://www.baeldung.com/java-swagger-api-description-deprecated) - [Set List of Objects in Swagger API Response](https://www.baeldung.com/java-swagger-set-list-response) - [Configure JWT Authentication for OpenAPI](https://www.baeldung.com/openapi-jwt-authentication) +- [Apply Default Global SecurityScheme in springdoc-openapi](https://www.baeldung.com/spring-openapi-global-securityscheme) From 594cfc0c0ecddf53ea67c9657b20785befd43ee8 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Thu, 28 Jul 2022 23:30:32 +0500 Subject: [PATCH 28/42] Updated README.md added link back to the article: https://www.baeldung.com/maven-snapshot-release-repository --- maven-modules/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/maven-modules/README.md b/maven-modules/README.md index 19f0473a58..29a69d37e4 100644 --- a/maven-modules/README.md +++ b/maven-modules/README.md @@ -8,3 +8,4 @@ This module contains articles about Apache Maven. Please refer to its submodules - [Apache Maven Standard Directory Layout](https://www.baeldung.com/maven-directory-structure) - [Multi-Module Project with Maven](https://www.baeldung.com/maven-multi-module) - [Maven Packaging Types](https://www.baeldung.com/maven-packaging-types) +- [Maven Snapshot Repository vs Release Repository](https://www.baeldung.com/maven-snapshot-release-repository) From e34a4ed1435fdd1a02a0ba7981519bb8f2b2fdd8 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Thu, 28 Jul 2022 23:36:27 +0500 Subject: [PATCH 29/42] Updated README.md added link back to the article: https://www.baeldung.com/spring-reactive-read-flux-into-inputstream --- spring-5-reactive-modules/spring-5-reactive-3/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-5-reactive-modules/spring-5-reactive-3/README.md b/spring-5-reactive-modules/spring-5-reactive-3/README.md index da44bf98fc..044b3db5f4 100644 --- a/spring-5-reactive-modules/spring-5-reactive-3/README.md +++ b/spring-5-reactive-modules/spring-5-reactive-3/README.md @@ -3,4 +3,5 @@ This module contains articles about reactive Spring 5. - [Logging a Reactive Sequence](https://www.baeldung.com/spring-reactive-sequence-logging) +- [Reading Flux Into a Single InputStream Using Spring Reactive WebClient](https://www.baeldung.com/spring-reactive-read-flux-into-inputstream) - More articles: [[<-- prev]](../spring-5-reactive-2) From a9115211bcc8aec0a010c0dd85e692257d5cfe5e Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Thu, 28 Jul 2022 23:40:22 +0500 Subject: [PATCH 30/42] Updated README.md added link back to the article: https://www.baeldung.com/spring-jms-testing --- spring-jms/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-jms/README.md b/spring-jms/README.md index fdeb64953a..666e32fa4b 100644 --- a/spring-jms/README.md +++ b/spring-jms/README.md @@ -4,3 +4,4 @@ This module contains articles about Spring with JMS ### Relevant Articles: - [Getting Started with Spring JMS](https://www.baeldung.com/spring-jms) +- [Testing Spring JMS](https://www.baeldung.com/spring-jms-testing) From 33c18f2cd5c5d2882699cd5cd9395794ba9c35ab Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr <40685729+ueberfuhr@users.noreply.github.com> Date: Thu, 28 Jul 2022 21:04:26 +0200 Subject: [PATCH 31/42] BAEL-5630: Spring Boot 3 Sample (#12449) * BAEL-5630: Spring Boot 3 Sample * BAEL-5630: Reorganize project in Maven profiles * BAEL-5630: Upgrade Maven PMD plugin version * BAEL-5630: Rename tests to follow naming conventions --- parent-boot-3/README.md | 3 + parent-boot-3/pom.xml | 97 ++++++++ pom.xml | 1 + spring-boot-modules/spring-boot-3/pom.xml | 128 ++++++++++ .../com/baeldung/sample/TodoApplication.java | 11 + .../sample/boundary/CorsConfiguration.java | 42 ++++ .../boundary/CorsConfigurationData.java | 25 ++ .../boundary/GlobalExceptionHandler.java | 22 ++ .../sample/boundary/TodoDtoMapper.java | 36 +++ .../sample/boundary/TodoRequestDto.java | 17 ++ .../sample/boundary/TodoResponseDto.java | 15 ++ .../sample/boundary/TodosController.java | 83 +++++++ .../DataInitializationConfigurationData.java | 14 ++ .../sample/control/NotFoundException.java | 5 + .../com/baeldung/sample/control/Todo.java | 24 ++ .../sample/control/TodoEntityMapper.java | 16 ++ .../sample/control/TodosInitializer.java | 28 +++ .../baeldung/sample/control/TodosService.java | 103 +++++++++ .../baeldung/sample/entity/TodoEntity.java | 53 +++++ .../sample/entity/TodosRepository.java | 9 + .../src/main/resources/application.yml | 21 ++ .../sample/boundary/TodosBoundaryLayer.java | 13 ++ .../TodosControllerApiIntegrationTest.java | 218 ++++++++++++++++++ .../sample/control/TodosControlLayer.java | 13 ++ ...sInitializerActivationIntegrationTest.java | 39 ++++ .../TodosServiceDatabaseIntegrationTest.java | 63 +++++ .../control/TodosServiceIntegrationTest.java | 126 ++++++++++ .../sample/shared/EnableBeanValidation.java | 15 ++ 28 files changed, 1240 insertions(+) create mode 100644 parent-boot-3/README.md create mode 100644 parent-boot-3/pom.xml create mode 100644 spring-boot-modules/spring-boot-3/pom.xml create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/TodoApplication.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfiguration.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfigurationData.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/GlobalExceptionHandler.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoDtoMapper.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoRequestDto.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoResponseDto.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodosController.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/DataInitializationConfigurationData.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/NotFoundException.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/Todo.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodoEntityMapper.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosInitializer.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosService.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodoEntity.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodosRepository.java create mode 100644 spring-boot-modules/spring-boot-3/src/main/resources/application.yml create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosBoundaryLayer.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosControllerApiIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosControlLayer.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosInitializerActivationIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceDatabaseIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/shared/EnableBeanValidation.java diff --git a/parent-boot-3/README.md b/parent-boot-3/README.md new file mode 100644 index 0000000000..738d97bdfd --- /dev/null +++ b/parent-boot-3/README.md @@ -0,0 +1,3 @@ +## Parent Boot 2 + +This is a parent module for all projects using Spring Boot 3. diff --git a/parent-boot-3/pom.xml b/parent-boot-3/pom.xml new file mode 100644 index 0000000000..711096fec8 --- /dev/null +++ b/parent-boot-3/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + parent-boot-3 + 0.0.1-SNAPSHOT + parent-boot-3 + pom + Parent for all Spring Boot 3 modules + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + ${start-class} + + + + + + repackage + + + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + 3.0.0-M3 + 5.8.2 + 3.0.0-M7 + 1.18.22 + 17 + 3.17.0 + + + diff --git a/pom.xml b/pom.xml index 6f727d0dd0..a4f6a744ea 100644 --- a/pom.xml +++ b/pom.xml @@ -1252,6 +1252,7 @@ quarkus-modules/quarkus-jandex spring-boot-modules/spring-boot-cassandre spring-boot-modules/spring-boot-camel + spring-boot-modules/spring-boot-3 testing-modules/testing-assertions persistence-modules/fauna lightrun diff --git a/spring-boot-modules/spring-boot-3/pom.xml b/spring-boot-modules/spring-boot-3/pom.xml new file mode 100644 index 0000000000..556168e205 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + spring-boot-3-sample + 0.0.1-SNAPSHOT + spring-boot-3-sample + Demo project for Spring Boot + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + runtime + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + org.mapstruct + mapstruct + ${mapstruct.version} + true + + + org.springframework.boot + spring-boot-starter-test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 1.5.2.Final + com.baeldung.sample.TodoApplication + + + diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/TodoApplication.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/TodoApplication.java new file mode 100644 index 0000000000..72c9c0e482 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/TodoApplication.java @@ -0,0 +1,11 @@ +package com.baeldung.sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +@SpringBootApplication +public class TodoApplication { + public static void main(String[] args) { + SpringApplication.run(TodoApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfiguration.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfiguration.java new file mode 100644 index 0000000000..e02e3d5442 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfiguration.java @@ -0,0 +1,42 @@ +package com.baeldung.sample.boundary; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import static java.util.Arrays.stream; +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.ACCEPT_LANGUAGE; +import static org.springframework.http.HttpHeaders.CONTENT_LANGUAGE; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpHeaders.IF_MATCH; +import static org.springframework.http.HttpHeaders.IF_NONE_MATCH; +import static org.springframework.http.HttpHeaders.LINK; +import static org.springframework.http.HttpHeaders.LOCATION; +import static org.springframework.http.HttpHeaders.ORIGIN; + +@Configuration +public class CorsConfiguration { + + @Bean + public WebMvcConfigurer corsConfigurer(final CorsConfigurationData allowed) { + return new WebMvcConfigurer() { + + @Override + public void addCorsMappings(final CorsRegistry registry) { + registry.addMapping("/**") + .exposedHeaders(LOCATION, LINK) + // allow all HTTP request methods + .allowedMethods(stream(RequestMethod.values()).map(Enum::name).toArray(String[]::new)) // + // allow the commonly used headers + .allowedHeaders(ORIGIN, CONTENT_TYPE, CONTENT_LANGUAGE, ACCEPT, ACCEPT_LANGUAGE, IF_MATCH, IF_NONE_MATCH) // + // this is stage specific + .allowedOrigins(allowed.getOrigins()) + .allowCredentials(allowed.isCredentials()); + } + }; + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfigurationData.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfigurationData.java new file mode 100644 index 0000000000..1c4ef55e6c --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/CorsConfigurationData.java @@ -0,0 +1,25 @@ +package com.baeldung.sample.boundary; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * The properties from application.yml. You can specify them by the following snippet: + * + *
+ * server:
+ *   endpoints:
+ *     api:
+ *       v1: /api/v1
+ * 
+ */ +@Configuration +@ConfigurationProperties(prefix = "cors.allow") +@Data +public class CorsConfigurationData { + + private String[] origins = { "*" }; + private boolean credentials = false; + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/GlobalExceptionHandler.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/GlobalExceptionHandler.java new file mode 100644 index 0000000000..eb5435656e --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/GlobalExceptionHandler.java @@ -0,0 +1,22 @@ +package com.baeldung.sample.boundary; + +import com.baeldung.sample.control.NotFoundException; +import jakarta.validation.ValidationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + protected void handleNotFoundException() {} + + @ExceptionHandler({ValidationException.class, MethodArgumentNotValidException.class}) + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + protected void handleValidationException() {} + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoDtoMapper.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoDtoMapper.java new file mode 100644 index 0000000000..a870eade7b --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoDtoMapper.java @@ -0,0 +1,36 @@ +package com.baeldung.sample.boundary; + +import com.baeldung.sample.control.Todo; +import org.mapstruct.Mapper; + +import java.util.Locale; + +/** + * Dieser Mapper kopiert die Informationen zwischen den Schichten. + */ +@Mapper(componentModel = "spring") +interface TodoDtoMapper { + + TodoResponseDto map(Todo todo); + + default String _mapStatus(Todo.Status status) { + return switch (status) { + case NEW -> "new"; + case PROGRESS -> "progress"; + case COMPLETED -> "completed"; + case ARCHIVED -> "archived"; + }; + } + + Todo map(TodoRequestDto todo, Long id); + + default Todo.Status _mapStatus(String status) { + return null == status ? Todo.Status.NEW : switch (status) { + case "progress" -> Todo.Status.PROGRESS; + case "completed" -> Todo.Status.COMPLETED; + case "archived" -> Todo.Status.ARCHIVED; + default -> Todo.Status.NEW; + }; + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoRequestDto.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoRequestDto.java new file mode 100644 index 0000000000..fb7e646772 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoRequestDto.java @@ -0,0 +1,17 @@ +package com.baeldung.sample.boundary; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class TodoRequestDto { + @NotBlank + private String title; + private String description; + private LocalDate dueDate; + @Pattern(regexp = "new|progress|completed|archived") + private String status; +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoResponseDto.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoResponseDto.java new file mode 100644 index 0000000000..140db7b296 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodoResponseDto.java @@ -0,0 +1,15 @@ +package com.baeldung.sample.boundary; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class TodoResponseDto { + private Long id; + private String title; + private String description; + private LocalDate dueDate; + private String status; + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodosController.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodosController.java new file mode 100644 index 0000000000..7efa7dfee3 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/boundary/TodosController.java @@ -0,0 +1,83 @@ +package com.baeldung.sample.boundary; + +import com.baeldung.sample.control.NotFoundException; +import com.baeldung.sample.control.TodosService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; +import java.util.Collection; +import java.util.stream.Collectors; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; +import static org.springframework.http.HttpStatus.NO_CONTENT; + +@RestController +@RequestMapping("/api/v1/todos") +@RequiredArgsConstructor +public class TodosController { + + private static final String DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_VALUE; + + private final TodosService service; + // Mapping zwischen den Schichten + private final TodoDtoMapper mapper; + + @GetMapping(produces = DEFAULT_MEDIA_TYPE) + public Collection findAll() { + return service.findAll().stream() + .map(mapper::map) + .collect(Collectors.toList()); + } + + @GetMapping(value = "/{id}", produces = DEFAULT_MEDIA_TYPE) + public TodoResponseDto findById( + @PathVariable("id") final Long id + ) { + // Action + return service.findById(id) // + .map(mapper::map) // map to dto + .orElseThrow(NotFoundException::new); + } + + @PostMapping(consumes = DEFAULT_MEDIA_TYPE) + public ResponseEntity create(final @Valid @RequestBody TodoRequestDto item) { + // Action + final var todo = mapper.map(item, null); + final var newTodo = service.create(todo); + final var result = mapper.map(newTodo); + // Response + final URI locationHeader = linkTo(methodOn(TodosController.class).findById(result.getId())).toUri(); // HATEOAS + return ResponseEntity.created(locationHeader).body(result); + } + + @PutMapping(value = "{id}", consumes = DEFAULT_MEDIA_TYPE) + @ResponseStatus(NO_CONTENT) + public void update( + @PathVariable("id") final Long id, + @Valid @RequestBody final TodoRequestDto item + ) { + service.update(mapper.map(item, id)); + } + + @DeleteMapping("/{id}") + @ResponseStatus(NO_CONTENT) + public void delete( + @PathVariable("id") final Long id + ) { + service.delete(id); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/DataInitializationConfigurationData.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/DataInitializationConfigurationData.java new file mode 100644 index 0000000000..5871325714 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/DataInitializationConfigurationData.java @@ -0,0 +1,14 @@ +package com.baeldung.sample.control; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "application.data") +@Data +public class DataInitializationConfigurationData { + + private boolean initializeOnStartup = true; + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/NotFoundException.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/NotFoundException.java new file mode 100644 index 0000000000..b06b54f547 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/NotFoundException.java @@ -0,0 +1,5 @@ +package com.baeldung.sample.control; + +public class NotFoundException extends RuntimeException { + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/Todo.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/Todo.java new file mode 100644 index 0000000000..a972a9a21a --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/Todo.java @@ -0,0 +1,24 @@ +package com.baeldung.sample.control; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.time.LocalDate; + +public record Todo( + Long id, + @NotNull @Size(min = 1) String title, + String description, + LocalDate dueDate, + @NotNull Status status +) { + + public enum Status { + NEW, PROGRESS, COMPLETED, ARCHIVED + } + + public Todo(Long id, String title) { + this(id, title, null, null, Status.NEW); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodoEntityMapper.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodoEntityMapper.java new file mode 100644 index 0000000000..37a74f7f85 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodoEntityMapper.java @@ -0,0 +1,16 @@ +package com.baeldung.sample.control; + +import com.baeldung.sample.entity.TodoEntity; +import org.mapstruct.Mapper; + +/** + * Dieser Mapper kopiert die Informationen zwischen den Schichten. + */ +@Mapper(componentModel = "spring") +interface TodoEntityMapper { + + TodoEntity map(Todo todo); + + Todo map(TodoEntity todo); + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosInitializer.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosInitializer.java new file mode 100644 index 0000000000..0c7b76d165 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosInitializer.java @@ -0,0 +1,28 @@ +package com.baeldung.sample.control; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TodosInitializer { + + private final TodosService service; + /* + * we cannot use @Profile("default") because + * we are not able inject the bean during test + * depending from the profile activation + */ + private final DataInitializationConfigurationData config; + + @EventListener(ContextRefreshedEvent.class) + public void initializeTodos() { + if (this.config.isInitializeOnStartup() && this.service.count() < 1) { + this.service.create(new Todo(null, "Deploy and run the application.")); + this.service.create(new Todo(null, "Enter some TODO items!")); + } + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosService.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosService.java new file mode 100644 index 0000000000..e10ef6440f --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/control/TodosService.java @@ -0,0 +1,103 @@ +package com.baeldung.sample.control; + +import com.baeldung.sample.entity.TodosRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; + +/** + * Ein Service ist ein Singleton auf Control Layer, der in der Boundary von mehreren (REST) Controllern gemeinsam genutzt werden kann. + * Dieser hat keinen Bezug mehr zu HTTP. + */ +@Service +@RequiredArgsConstructor +public class TodosService { + + private final TodoEntityMapper mapper; + private final TodosRepository repo; + + /** + * Gibt die Anzahl an Datensätzen zurück. + * @return die Anzahl an Datensätzen + */ + long count() { + return repo.count(); + } + + /** + * Gibt alle Todos zurück. + * + * @return eine unveränderliche Collection + */ + public Collection findAll() { + return repo.findAll().stream() + .map(mapper::map) + .collect(toList()); + } + + /** + * Durchsucht die Todos nach einer ID. + * + * @param id die ID + * @return das Suchergebnis + */ + public Optional findById(long id) { + return repo.findById(id) + .map(mapper::map); + } + + /** + * Fügt ein Item in den Datenbestand hinzu. Dabei wird eine ID generiert. + * + * @param item das anzulegende Item (ohne ID) + * @return das gespeicherte Item (mit ID) + * @throws IllegalArgumentException wenn das Item null oder die ID bereits belegt ist + */ + public Todo create(Todo item) { + if (null == item || null != item.id()) { + throw new IllegalArgumentException("item must exist without any id"); + } + return mapper.map(repo.save(mapper.map(item))); + } + + /** + * Aktualisiert ein Item im Datenbestand. + * + * @param item das zu ändernde Item mit ID + * @throws IllegalArgumentException + * wenn das Item oder dessen ID nicht belegt ist + * @throws NotFoundException + * wenn das Element mit der ID nicht gefunden wird + */ + public void update(Todo item) { + if (null == item || null == item.id()) { + throw new IllegalArgumentException("item must exist with an id"); + } + // remove separat, um nicht neue Einträge hinzuzufügen (put allein würde auch ersetzen) + if (repo.existsById(item.id())) { + repo.save(mapper.map(item)); + } else { + throw new NotFoundException(); + } + } + + /** + * Entfernt ein Item aus dem Datenbestand. + * + * @param id die ID des zu löschenden Items + * @throws NotFoundException + * wenn das Element mit der ID nicht gefunden wird + */ + public void delete(long id) { + if (repo.existsById(id)) { + repo.deleteById(id); + } else { + throw new NotFoundException(); + } + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodoEntity.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodoEntity.java new file mode 100644 index 0000000000..e551876584 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodoEntity.java @@ -0,0 +1,53 @@ +package com.baeldung.sample.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.time.LocalDate; + +/* + * Auch hier wird eine separate Klasse erstellt. + * Und auch hier geht es wieder um Unabhängigkeit der beiden Layer (Control und Persistence). + * So kann z.B. die Auflösung von Fremdschlüsseln (assignee, priority, topic) in der Persistence Layer erfolgen (JPA unterstützt das), oder aber auch erst in der Control Layer. + */ +@Entity(name = "todo") +@Table(name = "todos") +// we do not use @Data because hashCode() and equals() might influence JPA's behaviour +@NoArgsConstructor +@Getter +@Setter +@ToString +public class TodoEntity { + + public enum StatusEntity { + NEW, PROGRESS, COMPLETED, ARCHIVED + } + + @GeneratedValue(strategy = GenerationType.AUTO) + @Id + private Long id; + @NotNull + @Size(min = 1) + private String title; + private String description; + private LocalDate dueDate; + @Enumerated + @NotNull + private StatusEntity status = StatusEntity.NEW; + + public TodoEntity(Long id, String title) { + this.id = id; + this.title = title; + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodosRepository.java b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodosRepository.java new file mode 100644 index 0000000000..9f305a61c2 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/java/com/baeldung/sample/entity/TodosRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.sample.entity; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TodosRepository extends JpaRepository { + +} diff --git a/spring-boot-modules/spring-boot-3/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3/src/main/resources/application.yml new file mode 100644 index 0000000000..9a966a5bbd --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/main/resources/application.yml @@ -0,0 +1,21 @@ +spring: + mvc: + throw-exception-if-no-handler-found: true + jackson: + deserialization: + FAIL_ON_UNKNOWN_PROPERTIES: true + property-naming-strategy: SNAKE_CASE + jpa: + open-in-view: false + generate-ddl: true + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect +# Custom Properties +cors: + allow: + origins: ${CORS_ALLOWED_ORIGINS:*} + credentials: ${CORS_ALLOW_CREDENTIALS:false} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosBoundaryLayer.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosBoundaryLayer.java new file mode 100644 index 0000000000..39797bf2fb --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosBoundaryLayer.java @@ -0,0 +1,13 @@ +package com.baeldung.sample.boundary; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.ComponentScan; + +/** + * This class bundles all classes in the boundary layer. + * This includes also the generated mapper classes. + */ +@TestConfiguration +@ComponentScan(basePackageClasses = TodosBoundaryLayer.class) +public class TodosBoundaryLayer { +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosControllerApiIntegrationTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosControllerApiIntegrationTest.java new file mode 100644 index 0000000000..680b6c85bb --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/boundary/TodosControllerApiIntegrationTest.java @@ -0,0 +1,218 @@ +package com.baeldung.sample.boundary; + +import com.baeldung.sample.control.NotFoundException; +import com.baeldung.sample.control.Todo; +import com.baeldung.sample.control.TodosService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integrationstests der Todos REST API auf HTTP Layer. Die Anwendung wird als Black Box behandelt. + * Somit müssen alle Tests, die einen existierenden Datensatz benötigen, diesen im Test anlegen. + * Spring Boot startet den sog. "ApplicationContext" automatisch und bietet Möglichkeiten, die Anwendungsteile vom Test aus aufzurufen. + */ +@WebMvcTest +@ContextConfiguration(classes = TodosBoundaryLayer.class) +class TodosControllerApiIntegrationTest { + + private static final String BASEURL = "/api/v1/todos"; // URL to Resource + private static final String DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_VALUE; + + @MockBean + TodosService service; + @Autowired + MockMvc mvc; // testing by sending HTTP requests and verifying HTTP responses + @Autowired + ObjectMapper mapper; // used to render or parse JSON + + /* + * Testfall: + * - GET auf alle Todos -> 200 OK mit JSON + */ + @DisplayName("GET auf alle Daten (200 OK)") + @Test + void testFindAllTodos() throws Exception { + when(service.findAll()).thenReturn(List.of()); + mvc + .perform(get(BASEURL).accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(DEFAULT_MEDIA_TYPE)); + } + + /* + * Testfall: + * - einzelnes Todos auslesen -> kein Fehler + */ + @Test + void testFindById() throws Exception { + when(service.findById(1L)) + .thenReturn(Optional.of(new Todo(1L, "test"))); + mvc + .perform(get(BASEURL + "/1").accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(DEFAULT_MEDIA_TYPE)) + .andExpect(jsonPath("$.title").value("test")); + } + + /* + * Testfall: + * - einzelnes Todos auslesen -> 404 + */ + @Test + void testFindByIdNotExisting() throws Exception { + when(service.findById(1L)) + .thenReturn(Optional.empty()); + mvc + .perform(get(BASEURL + "/1").accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isNotFound()); + } + + /* + * Testfall: + * - Anlegen eines Todos per POST -> 201 mit Location Header + */ + @DisplayName("POST liefert 201 mit Location-Header") + @Test + void testCreateTodo() throws Exception { + // etwas umständlich, die ID zu besetzen, wenn auf dem Service create() aufgerufen wird + when(service.create(any())).thenReturn(new Todo(5L, "test-todo")); + final var json = "{\"title\":\"test-todo\"}"; + mvc + .perform(post(BASEURL).contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isCreated()) + .andExpect(header().exists(HttpHeaders.LOCATION)) + .andExpect(jsonPath("$.id").value(5L)); + } + + /* + * Testfall: + * - Anlegen eines Todos per POST ohne Titel -> 422 + */ + @DisplayName("POST erzeugt kein Todo, wenn kein Titel angegeben ist") + @Test + void testCreateTodoWithoutTitle() throws Exception { + final var json = "{}"; + mvc + .perform(post(BASEURL).contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isUnprocessableEntity()); + verifyNoInteractions(service); + } + + /* + * Testfall: + * - Anlegen eines Todos per POST mit ID -> 400 + */ + @DisplayName("POST erzeugt kein Todo, wenn die ID mitgegeben wird (undefinierte Property)") + @Test + void testCreateTodoWithID() throws Exception { + final var json = "{\"id\":1, \"title\":\"test\"}"; + mvc + .perform(post(BASEURL).contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isBadRequest()); + verifyNoInteractions(service); + } + + /* + * Testfall: + * - Anlegen eines Todos per POST mit leerem Titel -> 400 + */ + @DisplayName("POST erzeugt kein Todo, wenn Titel weniger als 1 Zeichen hat") + @Test + void testCreateTodoWithEmptyTitle() throws Exception { + final var newTodo = new TodoRequestDto(); + newTodo.setTitle(""); + final String json = mapper.writeValueAsString(newTodo); + this.mvc // + .perform(post(BASEURL).contentType(DEFAULT_MEDIA_TYPE).content(json)) // + .andExpect(status().isUnprocessableEntity()); + } + + /* + * Testfall: + * - Ändern -> 204 + */ + @Test + void testUpdateTodo() throws Exception { + final var json = "{\"title\":\"test-todo\"}"; + mvc + .perform(put(BASEURL + "/5").contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isNoContent()); + } + + /* + * Testfall: + * - Ändern -> 404 + */ + @Test + void testUpdateTodoNotExisting() throws Exception { + doThrow(NotFoundException.class).when(service).update(any()); + final var json = "{\"title\":\"test-todo\"}"; + mvc + .perform(put(BASEURL + "/5").contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isNotFound()); + } + + /* + * Testfall: + * - Ändern des Todos mit leerem Titel + */ + @Test + @DisplayName("PUT mit leerem Titel") + void testUpdateWithEmptyTitle() throws Exception { + // Act + final var json = "{}"; + mvc + .perform(put(BASEURL + "/5").contentType(DEFAULT_MEDIA_TYPE).content(json)) + .andExpect(status().isUnprocessableEntity()); + verifyNoInteractions(service); + } + + /* + * Testfall: + * - Löschen -> 204 + */ + @Test + void testDeleteTodo() throws Exception { + mvc + .perform(delete(BASEURL + "/5").contentType(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isNoContent()); + } + + /* + * Testfall: + * - Löschen -> 404 + */ + @Test + void testDeleteTodoNotExisting() throws Exception { + doThrow(NotFoundException.class).when(service).delete(anyLong()); + mvc + .perform(delete(BASEURL + "/5").contentType(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isNotFound()); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosControlLayer.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosControlLayer.java new file mode 100644 index 0000000000..ac07355eb9 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosControlLayer.java @@ -0,0 +1,13 @@ +package com.baeldung.sample.control; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.ComponentScan; + +/** + * This class bundles all classes in the control layer. + * This includes also the generated mapper classes. + */ +@TestConfiguration +@ComponentScan(basePackageClasses = TodosControlLayer.class) +public class TodosControlLayer { +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosInitializerActivationIntegrationTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosInitializerActivationIntegrationTest.java new file mode 100644 index 0000000000..ff28756e8e --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosInitializerActivationIntegrationTest.java @@ -0,0 +1,39 @@ +package com.baeldung.sample.control; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@SpringBootTest +class TodosInitializerActivationIntegrationTest { + + @MockBean + TodosService service; + + @BeforeEach + void serviceHasEmptyData() { + when(service.count()).thenReturn(0L); + } + + @AfterEach + void serviceHasNoFurtherInteractions() { + verifyNoMoreInteractions(service); + } + + @Test + @DisplayName("data initialization is invoked on default profile") + void testIsInvoked() { + verify(service).count(); + verify(service, atLeastOnce()).create(any()); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceDatabaseIntegrationTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceDatabaseIntegrationTest.java new file mode 100644 index 0000000000..4344e4ec3f --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceDatabaseIntegrationTest.java @@ -0,0 +1,63 @@ +package com.baeldung.sample.control; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@SpringBootTest +@AutoConfigureTestDatabase +@Transactional +@TestPropertySource( + properties = { + "application.data.initialize-on-startup=false" + } +) +class TodosServiceDatabaseIntegrationTest { + + @Autowired + TodosService service; + + @BeforeEach + void assertEmpty() { + assertThat(service.count()) + .isZero(); + } + + @Test + void testCreate() { + service.create(new Todo(null, "test")); + assertThat(service.count()) + .isEqualTo(1); + assertThat(service.findAll()) + .hasSize(1) + .element(0).extracting(Todo::title).isEqualTo("test"); + } + + @Test + void testFindById() { + final var todo = service.create(new Todo(null, "test")); + final var result = service.findById(todo.id()); + assertThat(result) + .isNotEmpty() + .get().usingRecursiveComparison().isEqualTo(todo); + } + + @Test + void testDelete() { + final var todo = service.create(new Todo(null, "test")); + final var id = todo.id(); + service.delete(id); + final var result = service.findById(id); + assertThat(result).isEmpty(); + assertThatThrownBy(() -> service.delete(id)) + .isInstanceOf(NotFoundException.class); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceIntegrationTest.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceIntegrationTest.java new file mode 100644 index 0000000000..49bf34ac80 --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/control/TodosServiceIntegrationTest.java @@ -0,0 +1,126 @@ +package com.baeldung.sample.control; + +import com.baeldung.sample.entity.TodoEntity; +import com.baeldung.sample.entity.TodosRepository; +import org.junit.jupiter.api.Test; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Integrationstests des TodosServices. Die Anwendung wird als Black Box behandelt. + * Somit müssen alle Tests, die einen existierenden Datensatz benötigen, diesen im Test anlegen. + */ +@SpringBootTest(classes = TodosControlLayer.class) +class TodosServiceIntegrationTest { + + // dieses Objekt wird als Mock instruiert + // Answers.RETURNS_MOCKS ist notwendig, da Methoden in Service-Init-Methode bereits aufgerufen werden + @MockBean(answer = Answers.RETURNS_MOCKS) + TodosRepository repo; + + @Autowired + TodosService service; + + /* + * Testfall: + * - alle Todos auslesen -> kein Fehler + */ + @Test + void testFindAllTodos() { + when(repo.findAll()).thenReturn(List.of(new TodoEntity(1L, "test"))); + final var result = service.findAll(); + assertThat(result).hasSize(1) // + .element(0).extracting(Todo::id, Todo::title).containsExactly(1L, "test"); + } + + /* + * Testfall: + * - Anlegen eines Todos -> ID besetzt + * - Auslesen -> gefunden mit entsprechenden Werten + */ + @Test + @SuppressWarnings("ConstantConditions") + void testCreateTodo() { + final var newTodo = new Todo(null, "test-todo"); + when(repo.save(any())).thenReturn(new TodoEntity(5L, "test-todo")); + // create + final var result = this.service.create(newTodo); + // find out id + assertThat(result).extracting(Todo::id).isEqualTo(5L); + verify(repo).save(refEq(new TodoEntity(null, "test-todo"))); + } + + /* + * Testfall: + * - Ändern eines bestehenden Todos + * - Aufruf der Repo-Methode und Rückgabewert prüfen + */ + @Test + @SuppressWarnings("ConstantConditions") + void testUpdateExisting() { + final var todo = new Todo(5L, "test-todo"); + when(repo.existsById(todo.id())).thenReturn(true); + // Test + this.service.update(todo); + // Assert + verify(repo).save(refEq(new TodoEntity(5L, "test-todo"))); + } + + /* + * Testfall: + * - Ändern eines nicht existenten Todos + * - Aufruf der Repo-Methode und Rückgabewert prüfen + */ + @Test + void testUpdateNotExisting() { + final var todo = new Todo(5L, "test-todo"); + when(repo.existsById(todo.id())).thenReturn(false); + // Test+Assert + assertThatThrownBy(() -> this.service.update(todo)) + .isInstanceOf(NotFoundException.class); + verify(repo).existsById(todo.id()); + verifyNoMoreInteractions(repo); + } + + /* + * Testfall: + * - Löschen eines existenten Todos + * - Aufruf der Repo-Methode und Rückgabewert prüfen + */ + @Test + void testDeleteExisting() { + when(repo.existsById(5L)).thenReturn(true); + // Test + this.service.delete(5L); + // Assert + verify(repo).deleteById(5L); + } + + /* + * Testfall: + * - Löschen eines nicht existenten Todos + * - Aufruf der Repo-Methode und Rückgabewert prüfen + */ + @Test + void testDeleteNotExisting() { + when(repo.existsById(5L)).thenReturn(false); + // Test+Assert + assertThatThrownBy(() -> this.service.delete(5L)) + .isInstanceOf(NotFoundException.class); + verify(repo).existsById(5L); + verifyNoMoreInteractions(repo); + } + +} diff --git a/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/shared/EnableBeanValidation.java b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/shared/EnableBeanValidation.java new file mode 100644 index 0000000000..5780ac46fe --- /dev/null +++ b/spring-boot-modules/spring-boot-3/src/test/java/com/baeldung/sample/shared/EnableBeanValidation.java @@ -0,0 +1,15 @@ +package com.baeldung.sample.shared; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +@TestConfiguration +public class EnableBeanValidation { + + @Bean + public MethodValidationPostProcessor validator() { + return new MethodValidationPostProcessor(); + } + +} From 67c9cfec23b07045b0888849dc78db2489d1ee45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?El=C3=A7im=20Duran?= <49587041+elcim@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:13:54 +0300 Subject: [PATCH 32/42] Bael-5645 - Scanning Java Annotations at Runtime (#12527) * BAEL-5645 - Annotation scanners using Spring context, Spring core, reflections, java reflection, and Jandex libraries are implemented * BAEL-5645 - Library versions are incremented in pom.xml * BAEL-5645 - Some refactoring and indentation adjustments * BAEL-5645 - Revert of annotation value in unit test * BAEL-5645 - Merge with the latest remote version (after resolution of the conflicts) * BAEL-5645 - Some refactoring due to review comments Co-authored-by: elcimduran --- .../scanner/jandexlib/JandexScannerService.java | 8 ++++++-- .../JavaReflectionsScannerService.java | 10 ++++++++-- .../reflectionslib/ReflectionsScannerService.java | 6 ++++-- ...ava => SpringContextAnnotationScannerService.java} | 11 +++++++---- .../SpringCoreAnnotationScannerService.java | 11 +++++++---- .../scanner/SampleAnnotationScannerUnitTest.java | 7 ++++--- 6 files changed, 36 insertions(+), 17 deletions(-) rename spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/{SpringBeanAnnotationScannerService.java => SpringContextAnnotationScannerService.java} (80%) diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/jandexlib/JandexScannerService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/jandexlib/JandexScannerService.java index fc10db223e..7ed41af65d 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/jandexlib/JandexScannerService.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/jandexlib/JandexScannerService.java @@ -27,7 +27,9 @@ public class JandexScannerService implements SampleAnnotationScanner { try { final IndexReader reader = new IndexReader(appFile.getInputStream()); Index jandexFile = reader.read(); - final List appAnnotationList = jandexFile.getAnnotations(DotName.createSimple("com.baeldung.annotation.scanner.SampleAnnotation")); + final List appAnnotationList = jandexFile + .getAnnotations(DotName + .createSimple("com.baeldung.annotation.scanner.SampleAnnotation")); List annotatedMethods = new ArrayList<>(); for (AnnotationInstance annotationInstance : appAnnotationList) { if (annotationInstance.target() @@ -48,7 +50,9 @@ public class JandexScannerService implements SampleAnnotationScanner { try { final IndexReader reader = new IndexReader(appFile.getInputStream()); Index jandexFile = reader.read(); - final List appAnnotationList = jandexFile.getAnnotations(DotName.createSimple("com.baeldung.annotation.scanner.SampleAnnotation")); + final List appAnnotationList = jandexFile + .getAnnotations(DotName + .createSimple("com.baeldung.annotation.scanner.SampleAnnotation")); List annotatedClasses = new ArrayList<>(); for (AnnotationInstance annotationInstance : appAnnotationList) { if (annotationInstance.target() diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/javareflectionlib/JavaReflectionsScannerService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/javareflectionlib/JavaReflectionsScannerService.java index 2833bb1326..76a2293965 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/javareflectionlib/JavaReflectionsScannerService.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/javareflectionlib/JavaReflectionsScannerService.java @@ -1,5 +1,6 @@ package com.baeldung.annotation.scanner.javareflectionlib; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; @@ -37,9 +38,14 @@ public class JavaReflectionsScannerService implements SampleAnnotationScanner { try { Class clazz = ClassLoader.getSystemClassLoader() .loadClass("com.baeldung.annotation.scanner.SampleAnnotatedClass"); - SampleAnnotation classAnnotation = clazz.getAnnotation(SampleAnnotation.class); List annotatedClasses = new ArrayList<>(); - annotatedClasses.add(classAnnotation.name()); + Annotation[] classAnnotations = clazz.getAnnotations(); + for (Annotation annotation : classAnnotations) { + if (annotation.annotationType() + .equals(SampleAnnotation.class)) { + annotatedClasses.add(((SampleAnnotation) annotation).name()); + } + } return Collections.unmodifiableList(annotatedClasses); } catch (ClassNotFoundException e) { throw new UnexpectedScanException(e); diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/reflectionslib/ReflectionsScannerService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/reflectionslib/ReflectionsScannerService.java index 82a10e21aa..9cee4f19e2 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/reflectionslib/ReflectionsScannerService.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/reflectionslib/ReflectionsScannerService.java @@ -16,7 +16,8 @@ public class ReflectionsScannerService implements SampleAnnotationScanner { @Override public List scanAnnotatedMethods() { Reflections reflections = new Reflections("com.baeldung.annotation.scanner"); - Set methods = reflections.getMethodsAnnotatedWith(SampleAnnotation.class); + Set methods = reflections + .getMethodsAnnotatedWith(SampleAnnotation.class); return methods.stream() .map(method -> method.getAnnotation(SampleAnnotation.class) .name()) @@ -26,7 +27,8 @@ public class ReflectionsScannerService implements SampleAnnotationScanner { @Override public List scanAnnotatedClasses() { Reflections reflections = new Reflections("com.baeldung.annotation.scanner"); - Set> types = reflections.getTypesAnnotatedWith(SampleAnnotation.class); + Set> types = reflections + .getTypesAnnotatedWith(SampleAnnotation.class); return types.stream() .map(clazz -> clazz.getAnnotation(SampleAnnotation.class) .name()) diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringBeanAnnotationScannerService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringContextAnnotationScannerService.java similarity index 80% rename from spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringBeanAnnotationScannerService.java rename to spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringContextAnnotationScannerService.java index cd31ae686e..8d52067ccc 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringBeanAnnotationScannerService.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcontextlib/SpringContextAnnotationScannerService.java @@ -17,7 +17,7 @@ import com.baeldung.annotation.scanner.SampleAnnotationScanner; import com.baeldung.annotation.scanner.ScanNotSupportedException; @Service -public class SpringBeanAnnotationScannerService implements SampleAnnotationScanner { +public class SpringContextAnnotationScannerService implements SampleAnnotationScanner { @Override public List scanAnnotatedMethods() { throw new ScanNotSupportedException(); @@ -25,13 +25,16 @@ public class SpringBeanAnnotationScannerService implements SampleAnnotationScann @Override public List scanAnnotatedClasses() { - ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + ClassPathScanningCandidateComponentProvider provider = + new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AnnotationTypeFilter(SampleAnnotation.class)); - Set beanDefs = provider.findCandidateComponents("com.baeldung.annotation.scanner"); + Set beanDefs = provider + .findCandidateComponents("com.baeldung.annotation.scanner"); List annotatedBeans = new ArrayList<>(); for (BeanDefinition bd : beanDefs) { if (bd instanceof AnnotatedBeanDefinition) { - Map annotAttributeMap = ((AnnotatedBeanDefinition) bd).getMetadata() + Map annotAttributeMap = ((AnnotatedBeanDefinition) bd) + .getMetadata() .getAnnotationAttributes(SampleAnnotation.class.getCanonicalName()); annotatedBeans.add(annotAttributeMap.get("name") .toString()); diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcorelib/SpringCoreAnnotationScannerService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcorelib/SpringCoreAnnotationScannerService.java index f3421fe46d..14542a9104 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcorelib/SpringCoreAnnotationScannerService.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/annotation/scanner/springcorelib/SpringCoreAnnotationScannerService.java @@ -8,21 +8,24 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Service; import org.springframework.util.ClassUtils; -import com.baeldung.annotation.scanner.SampleAnnotationScanner; import com.baeldung.annotation.scanner.SampleAnnotatedClass; import com.baeldung.annotation.scanner.SampleAnnotation; +import com.baeldung.annotation.scanner.SampleAnnotationScanner; import com.baeldung.annotation.scanner.ScanNotSupportedException; @Service public class SpringCoreAnnotationScannerService implements SampleAnnotationScanner { @Override public List scanAnnotatedMethods() { - final Class userClass = ClassUtils.getUserClass(SampleAnnotatedClass.class); - return Arrays.stream(userClass.getMethods()) - .filter(method -> AnnotationUtils.getAnnotation(method, SampleAnnotation.class) != null) + Class userClass = ClassUtils.getUserClass(SampleAnnotatedClass.class); + List annotatedMethods = Arrays.stream(userClass.getMethods()) + .filter(method -> AnnotationUtils + .getAnnotation(method, SampleAnnotation.class) != null) .map(method -> method.getAnnotation(SampleAnnotation.class) .name()) .collect(Collectors.toList()); + + return annotatedMethods; } @Override diff --git a/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/annotation/scanner/SampleAnnotationScannerUnitTest.java b/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/annotation/scanner/SampleAnnotationScannerUnitTest.java index 80eca2b4c5..7c3d01b5e8 100644 --- a/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/annotation/scanner/SampleAnnotationScannerUnitTest.java +++ b/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/annotation/scanner/SampleAnnotationScannerUnitTest.java @@ -28,7 +28,8 @@ public class SampleAnnotationScannerUnitTest { assertNotNull(annotatedClasses); assertEquals(4, annotatedClasses.size()); - annotatedClasses.forEach(annotValue -> assertEquals("SampleAnnotatedClass", annotValue)); + annotatedClasses.forEach(annotValue -> assertEquals("SampleAnnotatedClass", + annotValue)); } @Test @@ -41,7 +42,7 @@ public class SampleAnnotationScannerUnitTest { assertNotNull(annotatedMethods); assertEquals(3, annotatedMethods.size()); - annotatedMethods.forEach(annotValue -> assertEquals("annotatedMethod", annotValue)); + annotatedMethods.forEach(annotValue -> assertEquals("annotatedMethod", + annotValue)); } - } From e97b4c899add839de1741a5bd299855540b65401 Mon Sep 17 00:00:00 2001 From: anuragkumawat Date: Fri, 29 Jul 2022 19:14:40 +0530 Subject: [PATCH 33/42] JAVA-10376 Code Mismatch | Exploring the Spring 5 WebFlux URL Matching (#12509) * JAVA-10376 Code Mismatch | Exploring the Spring 5 WebFlux URL Matching * JAVA-10376 Code Mismatch | Exploring the Spring 5 WebFlux URL Matching --- ...eSpring5URLPatternUsingRouterFunctions.java | 7 ++++--- .../src/main/resources/resources/test/test.txt | 1 + ...ernUsingRouterFunctionsIntegrationTest.java | 18 ++++++++++++++---- ...ternsUsingHandlerMethodIntegrationTest.java | 14 +++++++++++--- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 spring-5-reactive-modules/spring-5-reactive/src/main/resources/resources/test/test.txt diff --git a/spring-5-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctions.java b/spring-5-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctions.java index 34abada2f1..b7bb53600e 100644 --- a/spring-5-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctions.java +++ b/spring-5-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctions.java @@ -24,11 +24,12 @@ public class ExploreSpring5URLPatternUsingRouterFunctions { private RouterFunction routingFunction() { - return route(GET("/p?ths"), serverRequest -> ok().body(fromValue("/p?ths"))).andRoute(GET("/test/{*id}"), serverRequest -> ok().body(fromValue(serverRequest.pathVariable("id")))) - .andRoute(GET("/*card"), serverRequest -> ok().body(fromValue("/*card path was accessed"))) + return route(GET("/t?st"), serverRequest -> ok().body(fromValue("Path /t?st is accessed"))).andRoute(GET("/test/{*id}"), serverRequest -> ok().body(fromValue(serverRequest.pathVariable("id")))) + .andRoute(GET("/baeldung/*Id"), serverRequest -> ok().body(fromValue("/baeldung/*Id path was accessed"))) .andRoute(GET("/{var1}_{var2}"), serverRequest -> ok().body(fromValue(serverRequest.pathVariable("var1") + " , " + serverRequest.pathVariable("var2")))) .andRoute(GET("/{baeldung:[a-z]+}"), serverRequest -> ok().body(fromValue("/{baeldung:[a-z]+} was accessed and baeldung=" + serverRequest.pathVariable("baeldung")))) - .and(RouterFunctions.resources("/files/{*filepaths}", new ClassPathResource("files/"))); + .and(RouterFunctions.resources("/files/{*filepaths}", new ClassPathResource("files/"))) + .and(RouterFunctions.resources("/resources/**", new ClassPathResource("resources/"))); } WebServer start() throws Exception { diff --git a/spring-5-reactive-modules/spring-5-reactive/src/main/resources/resources/test/test.txt b/spring-5-reactive-modules/spring-5-reactive/src/main/resources/resources/test/test.txt new file mode 100644 index 0000000000..30d74d2584 --- /dev/null +++ b/spring-5-reactive-modules/spring-5-reactive/src/main/resources/resources/test/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest.java b/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest.java index 91721d2cef..a77a67c6ba 100644 --- a/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest.java +++ b/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest.java @@ -27,12 +27,12 @@ public class ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest { @Test public void givenRouter_whenGetPathWithSingleCharWildcard_thenGotPathPattern() throws Exception { client.get() - .uri("/paths") + .uri("/test") .exchange() .expectStatus() .isOk() .expectBody(String.class) - .isEqualTo("/p?ths"); + .isEqualTo("Path /t?st is accessed"); } @Test @@ -50,12 +50,12 @@ public class ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest { public void givenRouter_whenGetMultipleCharWildcard_thenGotPathPattern() throws Exception { client.get() - .uri("/wildcard") + .uri("/baeldung/tutorialId") .exchange() .expectStatus() .isOk() .expectBody(String.class) - .isEqualTo("/*card path was accessed"); + .isEqualTo("/baeldung/*Id path was accessed"); } @Test @@ -107,4 +107,14 @@ public class ExploreSpring5URLPatternUsingRouterFunctionsIntegrationTest { .isEqualTo("hello"); } + @Test + public void givenRouter_whenAccess_thenGot() throws Exception { + client.get() + .uri("/resources/test/test.txt") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("test"); + } } diff --git a/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/PathPatternsUsingHandlerMethodIntegrationTest.java b/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/PathPatternsUsingHandlerMethodIntegrationTest.java index d4c1cfe4c8..0b4607b54a 100644 --- a/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/PathPatternsUsingHandlerMethodIntegrationTest.java +++ b/spring-5-reactive-modules/spring-5-reactive/src/test/java/com/baeldung/reactive/urlmatch/PathPatternsUsingHandlerMethodIntegrationTest.java @@ -27,13 +27,21 @@ public class PathPatternsUsingHandlerMethodIntegrationTest { @Test public void givenHandlerMethod_whenMultipleURIVariablePattern_then200() { - client.get() - .uri("/spring5/ab/cd") + client.get() + .uri("/spring5/baeldung/tutorial") .exchange() .expectStatus() .is2xxSuccessful() .expectBody() - .equals("/ab/cd"); + .equals("/baeldung/tutorial"); + + client.get() + .uri("/spring5/baeldung") + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody() + .equals("/baeldung"); } @Test From 067cddb33e8ebe2b40212b95a79d54c9f565a1b3 Mon Sep 17 00:00:00 2001 From: "thibault.faure" Date: Fri, 29 Jul 2022 21:44:04 +0200 Subject: [PATCH 34/42] Fix failed integration tests --- spring-boot-modules/spring-boot-mvc-3/pom.xml | 7 ------- .../java/com/baeldung/etag/EtagIntegrationTest.java | 2 -- .../DataProducerControllerIntegrationTest.java | 13 ++----------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/spring-boot-modules/spring-boot-mvc-3/pom.xml b/spring-boot-modules/spring-boot-mvc-3/pom.xml index f2b6c129f8..6b0477cfc8 100644 --- a/spring-boot-modules/spring-boot-mvc-3/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-3/pom.xml @@ -47,11 +47,4 @@
- - - - /src/main/resources - - - \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/etag/EtagIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/etag/EtagIntegrationTest.java index 97de6d06f1..d7b50cb7fb 100644 --- a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/etag/EtagIntegrationTest.java +++ b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/etag/EtagIntegrationTest.java @@ -9,7 +9,6 @@ import org.assertj.core.util.Preconditions; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; @@ -26,7 +25,6 @@ import static org.junit.Assert.assertTrue; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ComponentScan(basePackageClasses = WebConfig.class) -@EnableAutoConfiguration public class EtagIntegrationTest { @LocalServerPort diff --git a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java index 29f794645a..eb459d88cc 100644 --- a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java +++ b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/produceimage/DataProducerControllerIntegrationTest.java @@ -1,32 +1,23 @@ package com.baeldung.produceimage; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import org.springframework.web.context.WebApplicationContext; - @SpringBootTest(classes = ImageApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc public class DataProducerControllerIntegrationTest { @Autowired - private WebApplicationContext webApplicationContext; - private MockMvc mockMvc; - @BeforeEach - public void setup() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - } - @Test void givenJpgTrue_whenGetImageDynamicType_ThenContentTypeIsJpg() throws Exception { mockMvc.perform(get("/get-image-dynamic-type?jpg=true")) From 300d9408f694d644b28f2652c69c2e60f606884e Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Sat, 30 Jul 2022 06:26:40 +0500 Subject: [PATCH 35/42] Updated README.md Updated link title in README from 'Guide to QuarkusIO' to 'Guide to Quarkus' --- quarkus-modules/quarkus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quarkus-modules/quarkus/README.md b/quarkus-modules/quarkus/README.md index 94b71dd954..9e45cb1c81 100644 --- a/quarkus-modules/quarkus/README.md +++ b/quarkus-modules/quarkus/README.md @@ -1,4 +1,4 @@ ## Relevant Articles: -- [Guide to QuarkusIO](https://www.baeldung.com/quarkus-io) +- [Guide to Quarkus](https://www.baeldung.com/quarkus-io) - [Testing Quarkus Applications](https://www.baeldung.com/java-quarkus-testing) From be5e772318343aa6fa56d50cf408a0f8a703c2c6 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Sat, 30 Jul 2022 06:34:42 +0500 Subject: [PATCH 36/42] Updated README.md Updated link title in README from 'Sealed Classes and Interfaces in Java 17' to 'Sealed Classes and Interfaces in Java' --- core-java-modules/core-java-17/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-java-modules/core-java-17/README.md b/core-java-modules/core-java-17/README.md index d77a487932..9f39b0289f 100644 --- a/core-java-modules/core-java-17/README.md +++ b/core-java-modules/core-java-17/README.md @@ -5,4 +5,4 @@ - [Introduction to HexFormat in Java 17](https://www.baeldung.com/java-hexformat) - [New Features in Java 17](https://www.baeldung.com/java-17-new-features) - [Random Number Generators in Java 17](https://www.baeldung.com/java-17-random-number-generators) -- [Sealed Classes and Interfaces in Java 17](https://www.baeldung.com/java-sealed-classes-interfaces) +- [Sealed Classes and Interfaces in Java](https://www.baeldung.com/java-sealed-classes-interfaces) From 065846c376798a2d2c0396c38358baae5558fe6a Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Sat, 30 Jul 2022 06:46:34 +0500 Subject: [PATCH 37/42] Updated README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated title from 'Hibernate's “Detached Entity Passed to Persist” Error' to 'Hibernate’s “Detached Entity Passed to Persist” Error' --- persistence-modules/hibernate-exceptions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence-modules/hibernate-exceptions/README.md b/persistence-modules/hibernate-exceptions/README.md index 4acd6cd363..0225d3a753 100644 --- a/persistence-modules/hibernate-exceptions/README.md +++ b/persistence-modules/hibernate-exceptions/README.md @@ -6,4 +6,4 @@ - [Hibernate’s “Object References an Unsaved Transient Instance” Error](https://www.baeldung.com/hibernate-unsaved-transient-instance-error) - [EntityNotFoundException in Hibernate](https://www.baeldung.com/hibernate-entitynotfoundexception) - [Hibernate’s “Not-Null Property References a Null or Transient Value” Error](https://www.baeldung.com/hibernate-not-null-error) -- [Hibernate's “Detached Entity Passed to Persist” Error](https://www.baeldung.com/hibernate-detached-entity-passed-to-persist) +- [Hibernate’s “Detached Entity Passed to Persist” Error](https://www.baeldung.com/hibernate-detached-entity-passed-to-persist) From ef758acef0b9852575bf483981923c345caadff4 Mon Sep 17 00:00:00 2001 From: lalitrajput72 <31237283+lalitrajput72@users.noreply.github.com> Date: Sat, 30 Jul 2022 16:09:02 +0530 Subject: [PATCH 38/42] [BAEL-5622] static vs instance initializer block (#12271) * Deep copy vs Shallow copy Code commit * Static and instance block * Deep copy branch chanaged from master to other * static vs instance block * Update InstanceBlockExample.java * Update StaticBlockExample.java Co-authored-by: Lalit Rajput Co-authored-by: paritoshsunny --- .../instanceblock/InstanceBlockExample.java | 22 +++++++++++++++++++ .../staticblock/StaticBlockExample.java | 17 ++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/instanceblock/InstanceBlockExample.java create mode 100644 core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/staticblock/StaticBlockExample.java diff --git a/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/instanceblock/InstanceBlockExample.java b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/instanceblock/InstanceBlockExample.java new file mode 100644 index 0000000000..c17fd8d3e6 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/instanceblock/InstanceBlockExample.java @@ -0,0 +1,22 @@ +package com.baeldung.initializerblock.instanceblock; + +public class InstanceBlockExample { + + { + System.out.println("Instance initializer block 1"); + } + + { + System.out.println("Instance initializer block 2"); + } + + public InstanceBlockExample() { + System.out.println("Class constructor"); + } + + public static void main(String[] args) { + InstanceBlockExample iib = new InstanceBlockExample(); + System.out.println("Main Method"); + } +} + diff --git a/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/staticblock/StaticBlockExample.java b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/staticblock/StaticBlockExample.java new file mode 100644 index 0000000000..a409b6d7c8 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/initializerblock/staticblock/StaticBlockExample.java @@ -0,0 +1,17 @@ +package com.baeldung.initializerblock.staticblock; + +public class StaticBlockExample { + + static { + System.out.println("static block 1"); + } + + static { + System.out.println("static block 2"); + } + + public static void main(String[] args) { + System.out.println("Main Method"); + } +} + From d0ba47e75dede5fa71c22bc0ab0e831ec41edf1d Mon Sep 17 00:00:00 2001 From: ACHRAF TAITAI <43656331+achraftt@users.noreply.github.com> Date: Sat, 30 Jul 2022 14:22:59 +0200 Subject: [PATCH 39/42] BAEL-5643 : Replace at Specific Index in Java ArrayList (#12539) --- .../ReplaceUsingIndexInArrayListUnitTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core-java-modules/core-java-collections-list-4/src/test/java/com/baeldung/list/replace/ReplaceUsingIndexInArrayListUnitTest.java diff --git a/core-java-modules/core-java-collections-list-4/src/test/java/com/baeldung/list/replace/ReplaceUsingIndexInArrayListUnitTest.java b/core-java-modules/core-java-collections-list-4/src/test/java/com/baeldung/list/replace/ReplaceUsingIndexInArrayListUnitTest.java new file mode 100644 index 0000000000..ad6d0fee43 --- /dev/null +++ b/core-java-modules/core-java-collections-list-4/src/test/java/com/baeldung/list/replace/ReplaceUsingIndexInArrayListUnitTest.java @@ -0,0 +1,21 @@ +package com.baeldung.list.replace; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReplaceUsingIndexInArrayListUnitTest { + + private static final List EXPECTED = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); + + @Test void givenArrayList_updateUsingSet() { + List aList = new ArrayList<>(Arrays.asList(1, 2, 7, 4, 5)); + aList.set(2, 3); + assertThat(aList).isEqualTo(EXPECTED); + } +} \ No newline at end of file From a313c855d32b0ac6e7ee417d104ee8dab10c6304 Mon Sep 17 00:00:00 2001 From: Thiago dos Santos Hora Date: Sat, 30 Jul 2022 14:26:16 +0200 Subject: [PATCH 40/42] [BAEL-5631] Quarkus vs Spring boot improvements (#12520) * Initial impl * Update framework versions testing from jm to wrk * Add hyperfoil * Add hyperfoil read me --- .../quarkus-vs-springboot/README.md | 54 +++- .../hyperfoil/docker_run.sh | 13 + .../hyperfoil/volume/benchmark.hf.yaml | 86 ++++++ .../{ => hyperfoil/volume}/cities.csv | 0 .../quarkus-vs-springboot/jmeter/cities.csv | 136 +++++++++ .../{ => jmeter}/load_test.jmx | 0 .../run_test_jmeter.sh} | 0 .../quarkus-project/build.sh | 8 +- .../quarkus-project/pom.xml | 277 +++++++++--------- .../src/main/docker/Dockerfile.jvm | 3 +- .../src/main/docker/quarkus.yml | 30 +- .../baeldung/quarkus_project/ZipCodeRepo.java | 4 +- .../quarkus_project/ZipCodeResource.java | 16 +- .../src/main/resources/application.properties | 13 +- .../spring-project/build.sh | 11 + .../spring-project/build_jvm_docker.sh | 6 - .../spring-project/pom.xml | 229 ++++++++++----- .../src/main/docker/Dockerfile.jvm | 12 - .../spring-project/src/main/docker/spring.yml | 30 +- .../com/baeldung/spring_project/Startup.java | 6 +- .../baeldung/spring_project/ZipCodeApi.java | 24 +- .../src/main/resources/application.properties | 12 +- .../baeldung/spring_project/StartupIT.java | 13 +- .../quarkus-vs-springboot/wrk/cities.csv | 136 +++++++++ .../quarkus-vs-springboot/wrk/generator.lua | 65 ++++ .../quarkus-vs-springboot/wrk/get_by_city.lua | 79 +++++ .../quarkus-vs-springboot/wrk/get_zipcode.lua | 77 +++++ .../quarkus-vs-springboot/wrk/json.lua | 133 +++++++++ .../wrk/post_zipcode.lua | 73 +++++ .../quarkus-vs-springboot/wrk/run_test_wrk.sh | 14 + 30 files changed, 1258 insertions(+), 302 deletions(-) create mode 100644 quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh create mode 100644 quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml rename quarkus-modules/quarkus-vs-springboot/{ => hyperfoil/volume}/cities.csv (100%) create mode 100644 quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv rename quarkus-modules/quarkus-vs-springboot/{ => jmeter}/load_test.jmx (100%) rename quarkus-modules/quarkus-vs-springboot/{run_test.sh => jmeter/run_test_jmeter.sh} (100%) create mode 100755 quarkus-modules/quarkus-vs-springboot/spring-project/build.sh delete mode 100644 quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh delete mode 100644 quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/cities.csv create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/generator.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/json.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua create mode 100755 quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh diff --git a/quarkus-modules/quarkus-vs-springboot/README.md b/quarkus-modules/quarkus-vs-springboot/README.md index 05eaabb923..13c0b8ab5f 100644 --- a/quarkus-modules/quarkus-vs-springboot/README.md +++ b/quarkus-modules/quarkus-vs-springboot/README.md @@ -6,6 +6,9 @@ To follow this tutorial, you will need the following things: - Maven (Embedded, IDE, or local installation) - Docker (https://www.docker.com/) - Jmeter (https://jmeter.apache.org/) +- wrk (https://github.com/wg/wrk) +- hyperfoil (https://hyperfoil.io/) +- lua (https://www.lua.org/) To create this test, I used some custom features from Jmeter. You can install the Jmeter plugin manager here: https://loadium.com/blog/how-to-install-use-jmeter-plugin. After that, please install the following plugins: @@ -17,31 +20,32 @@ The test file is `load_test.jmx` in case of any change need. You can open it wit $jmeter_home/bin/jmeter -n -t load_test.jmx -l log.csv -e -o ./report ``` -Just remember to change the variable `jmeter_home` with the path to the JMeter folder. The path to the data files is relative, so either keep them in the same folder as the test or use Jmeter GUI to change it. +Just remember to change the variable `jmeter_home` with the path to the JMeter folder. The path to the data files is relative, so either keep them in the same folder as the test or use Jmeter GUI to change it. Rememeber that as mentioned in the article, we cannot consider the response times recorded by Jmeter due to the Coordinated Omission Problem. Open the VisualVM application and select your application to start monitoring before running the test, and of course, start the sample application first. ## Spring Boot To build the application, you only need to run the following command in the Spring project root: ``` -./mvnw package -f pom.xml +./mvnw clean package -f pom.xml ``` Or this one in case you want to build the native one: ``` -./mvnw -DskipTests package -Pnative -f pom.xml +./mvnw clean package -Pnative -f pom.xml ``` In this case, you will need to have the `GRAALVM_HOME` env variable defined. You only need this if you want to build the image locally. Otherwise, you can build it using docker by leveraging the Spring Boot maven plugin. It will pull a docker image of the GraalVM, and with that, it will create the native image of the app. To do that, run: ``` -./mvnw spring-boot:build-image +./mvnw clean package spring-boot:build-image -Pnative -f pom.xml ``` -You can also create a docker image with the JVM version of the app running the script `build_jvm_docker.sh` or: +You can also create a docker image with the JVM version one of the app running the script `build.sh` or: ``` -docker build -f src/main/docker/Dockerfile.jvm -t spring-project:0.1-SNAPSHOT . +./mvnw clean package spring-boot:build-image -f pom.xml + ``` -You can execute the script `start_app.sh` or `start_jvm.sh` to run the application locally. In this case, you will need the Postgres DB. You can run it in docker with the command: +You can execute the script `start_app.sh` or `start_jvm.sh` to run the application locally. In this case, you will need the Mysql DB. You can run it in docker with the command: ``` -docker run -e POSTGRES_PASSWORD=example -p 5432:5432 postgres +docker run --name mysqldb --network=host -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=baeldung -d mysql:5.7.38 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ``` You can also run both application and DB from docker, using: ``` @@ -67,7 +71,7 @@ And to the JVM version: To start the application locally, use either the scripts `start_app.sh` and `start_jvm.sh` with the docker DB: ``` -docker run -e POSTGRES_PASSWORD=example -p 5432:5432 postgres +docker run --name mysqldb --network=host -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=baeldung -d mysql:5.7.38 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ``` Or use the script to build the docker image of the application, running: ```bash @@ -94,6 +98,38 @@ docker-compose -f src/main/docker/quarkus.yml up Now you have all you need to reproduce the tests with your machine. +## Wrk +Another option to execute the load test is to use the wrk. This library is capable of generation a pretty high load only using a single core. To install it you only have to checkout the project compile it (using make) and define the `wrk_home` envvar. To run the test use: + +``` +./run_test_wrk.sh +``` +You will need to have installed lua in your machine. + +### Tips +If you want to run the applications in your machine you can use the following command to restrict the CPUs available to the app: + +``` +cpulimit -l 300 -p ## 300 means at most 3 cores. +``` + +This will make sure the load is on the application and not in the DB. +## Hyperfoil + +To the hyperfoil test to get a report regarding the performance of the application, its throughput and response time. You can run the `docker_run.sh` from the hyperfoil folder, or the following: + +``` +docker run -it -v volume:/benchmarks:Z -v tmp/reports:/tmp/reports:Z --network=host quay.io/hyperfoil/hyperfoil cli +``` +And then: +``` +start-local && upload /benchmarks/benchmark.hf.yaml && run benchmark +``` +Optionally, we can extract a html report from it, by running: +``` +report --destination=/tmp/reports +``` + ### Relevant Articles: - [Spring Boot vs Quarkus](https://www.baeldung.com/spring-boot-vs-quarkus) diff --git a/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh b/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh new file mode 100644 index 0000000000..ee0ee35a29 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +docker run -it -v $SCRIPTPATH/volume:/benchmarks:Z -v $SCRIPTPATH/tmp/reports:/tmp/reports:Z --network=host quay.io/hyperfoil/hyperfoil cli + +#start-local && upload /benchmarks/benchmark.hf.yaml && run benchmark + +# step 1 run: start-local +# step 2 run (Run this every time the file is modified): upload /benchmarks/benchmark.hf.yaml +# step 3 run: run benchmark +# step 4 run: stats +# step 5 run: report --destination=/tmp/reports diff --git a/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml new file mode 100644 index 0000000000..598c61249e --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml @@ -0,0 +1,86 @@ +name: benchmark +http: + host: http://localhost:8080 + sharedConnections: 100 +phases: + - main: + constantRate: + startAfter: rampup + usersPerSec: 3300 + maxSessions: 6000 + duration: 5m + forks: + - post_zipcode: &post_zipcode + scenario: + - fetchIndex: + - randomCsvRow: + file: /benchmarks/zip_code_database.csv + removeQuotes: true + columns: + 0: zip + 1: type + 3: city + 6: state + 7: county + 8: timezone + - httpRequest: + sla: + - blockedRatio: 500 + POST: /zipcode + headers: + Content-Type: application/json;charset=UTF-8 + Accept: application/json + body: | + { + "zip" : "${zip}", + "type" : "${type}", + "city" : "${city}", + "state" : "${state}", + "county" : "${county}", + "timezone" : "${timezone}" + } + - get_zipcode: &get_zipcode + scenario: + - fetchIndex: + - randomCsvRow: + file: /benchmarks/zip_code_database.csv + removeQuotes: true + columns: + 0: zipcode + - httpRequest: + sla: + - blockedRatio: 500 + headers: + accept: application/json + GET: /zipcode/${zipcode} + - get_zipcode_by_city: &get_zipcode_by_city + scenario: + - fetchDetails: + - randomCsvRow: + file: /benchmarks/cities.csv + removeQuotes: true + columns: + 0: city + - httpRequest: + sla: + - blockedRatio: 500 + headers: + accept: application/json + GET: /zipcode/by_city?city=${city} + - spike: + constantRate: + startAfter: main + usersPerSec: 4400 + duration: 2m + forks: + - get_zipcode_by_city: *get_zipcode_by_city + - get_zipcode: *get_zipcode + + - rampup: + increasingRate: + initialUsersPerSec: 3 + targetUsersPerSec: 2500 + duration: 1m + forks: + - post_zipcode: *post_zipcode + - get_zipcode: *get_zipcode diff --git a/quarkus-modules/quarkus-vs-springboot/cities.csv b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/cities.csv similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/cities.csv rename to quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/cities.csv diff --git a/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv b/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv new file mode 100644 index 0000000000..3b7016f3b5 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv @@ -0,0 +1,136 @@ +Holtsville +Adjuntas +Aguada +Aguadilla +Maricao +Anasco +Angeles +Arecibo +Bajadero +Barceloneta +Boqueron +Cabo Rojo +Penuelas +Camuy +Castaner +Rosario +Sabana Grande +Ciales +Utuado +Dorado +Ensenada +Florida +Garrochales +Guanica +Guayanilla +Hatillo +Hormigueros +Isabela +Jayuya +Lajas +Lares +Las Marias +Manati +Moca +Rincon +Quebradillas +Mayaguez +San German +San Sebastian +Morovis +Sabana Hoyos +San Antonio +Vega Alta +Vega Baja +Yauco +Aguas Buenas +Aguirre +Aibonito +Maunabo +Arroyo +Mercedita +Ponce +Naguabo +Naranjito +Orocovis +Palmer +Patillas +Caguas +Canovanas +Ceiba +Cayey +Fajardo +Cidra +Puerto Real +Punta Santiago +Roosevelt Roads +Rio Blanco +Rio Grande +Salinas +San Lorenzo +Santa Isabel +Vieques +Villalba +Yabucoa +Coamo +Las Piedras +Loiza +Luquillo +Culebra +Juncos +Gurabo +Coto Laurel +Comerio +Corozal +Guayama +La Plata +Humacao +Barranquitas +Juana Diaz +St Thomas +Christiansted +St John +Frederiksted +Kingshill +San Juan +Fort Buchanan +Toa Baja +Sabana Seca +Toa Alta +Bayamon +Catano +Guaynabo +Trujillo Alto +Saint Just +Carolina +Agawam +Amherst +Barre +Belchertown +Blandford +Bondsville +Brimfield +Chester +Chesterfield +Chicopee +Cummington +Easthampton +East Longmeadow +East Otis +Feeding Hills +Gilbertville +Goshen +Granby +Granville +Hadley +Hampden +Hardwick +Hatfield +Haydenville +Holyoke +Huntington +Leeds +Leverett +Ludlow +Monson +North Amherst \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/load_test.jmx b/quarkus-modules/quarkus-vs-springboot/jmeter/load_test.jmx similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/load_test.jmx rename to quarkus-modules/quarkus-vs-springboot/jmeter/load_test.jmx diff --git a/quarkus-modules/quarkus-vs-springboot/run_test.sh b/quarkus-modules/quarkus-vs-springboot/jmeter/run_test_jmeter.sh similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/run_test.sh rename to quarkus-modules/quarkus-vs-springboot/jmeter/run_test_jmeter.sh diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh b/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh index 85761adab0..22b6d5c9d4 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh @@ -2,12 +2,14 @@ SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -./mvnw quarkus:add-extension -Dextensions=container-image-docker +mvn quarkus:add-extension -Dextensions=container-image-docker if [ "$1" = "native" ]; then - ./mvnw package -Pnative -Dquarkus.native.container-build=true -f $SCRIPTPATH/pom.xml && + mvn clean package -Pnative -Dquarkus.native.container-build=true -f $SCRIPTPATH/pom.xml && docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.native -t quarkus-project:0.1-SNAPSHOT $SCRIPTPATH/. +elif [ "$1" = "local-native" ]; then + mvn clean package -DskipTests -Pnative -f $SCRIPTPATH/pom.xml else - ./mvnw package -Dquarkus.container-build=true -f $SCRIPTPATH/pom.xml && + mvn clean package -Dquarkus.container-build=true -f $SCRIPTPATH/pom.xml && docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.jvm -t quarkus-project:0.1-SNAPSHOT $SCRIPTPATH/. fi \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml b/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml index eeeb9d3256..8f28fde4a6 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml @@ -1,150 +1,143 @@ - - 4.0.0 - quarkus-project - 0.1-SNAPSHOT - - - com.baeldung - quarkus-vs-springboot - 1.0-SNAPSHOT - - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - + 4.0.0 + + com.baeldung + quarkus-vs-springboot + 1.0-SNAPSHOT + + quarkus-project + 0.1-SNAPSHOT + + 3.10.1 + true + 11 + 11 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 2.9.2.Final + 3.0.0-M6 + + - - io.quarkus - quarkus-hibernate-reactive-panache - - - io.quarkus - quarkus-resteasy-reactive - - - io.quarkus - quarkus-resteasy-reactive-jackson - - - io.quarkus - quarkus-reactive-pg-client - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-container-image-docker - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + - - + + + + io.quarkus + quarkus-hibernate-reactive-panache + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-reactive-mysql-client + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-container-image-docker + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + ${maven.compiler.parameters} + + + + maven-surefire-plugin + ${surefire-plugin.version} + + false + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + - ${maven.compiler.parameters} + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + - - - maven-surefire-plugin - ${surefire-plugin.version} - - false - - org.jboss.logmanager.LogManager - - - + + + - - - - native - - - native - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - - - - - - - - - -H:+AllowVMInspection - native - - - - - - 3.8.1 - true - 11 - 11 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus.platform - 2.2.2.Final - 3.0.0-M4 - - - \ No newline at end of file +
+ + -H:+AllowVMInspection + native + + + + diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm index e5d6d4d851..63ba538a4a 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm @@ -41,7 +41,8 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. -ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.rmi.port=5001 -Dcom.sun.management.jmxremote.host=0.0.0.0 -Djava.rmi.server.hostname=0.0.0.0" + # We make four distinct layers so if there are application changes the library layers can be re-used COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ COPY --chown=1001 target/quarkus-app/*.jar /deployments/ diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml index 00bdcf9292..60e35b6cca 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml @@ -1,23 +1,25 @@ version: '3.1' - services: db: - image: postgres + image: mysql:5.7.38 ports: - - '5432:5432' + - '3306:3306' environment: - POSTGRES_PASSWORD: example + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: baeldung + command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ] + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD app: image: quarkus-project:0.1-SNAPSHOT - ports: - - '8080:8080' + network_mode: "host" environment: - DB_URL: postgresql://db:5432/postgres - links: - - "db" + DB_URL: mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true + HOST_HOSTNAME: ${EXTERNAL_IP} depends_on: - - "db" -networks: - default: - driver: bridge - + db: + condition: service_healthy + deploy: + resources: + limits: + cpus: '3.00' diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java index 74f46c33ea..f6736a6e9e 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java @@ -1,6 +1,7 @@ package com.baeldung.quarkus_project; import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; +import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactional; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -13,7 +14,8 @@ public class ZipCodeRepo implements PanacheRepositoryBase { return find("city = ?1", city).stream(); } + @ReactiveTransactional public Uni save(ZipCode zipCode) { - return zipCode.persistAndFlush(); + return zipCode.persist(); } } diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java index b4d41fd855..cb9b0226f3 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java @@ -2,9 +2,8 @@ package com.baeldung.quarkus_project; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; -import org.jboss.logging.Logger; -import javax.transaction.Transactional; +import javax.persistence.PersistenceException; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -22,7 +21,7 @@ public class ZipCodeResource { @GET @Path("/{zipcode}") public Uni findById(@PathParam("zipcode") String zipcode) { - return zipRepo.findById(zipcode); + return getById(zipcode); } @GET @@ -32,12 +31,17 @@ public class ZipCodeResource { } @POST - @Transactional public Uni create(ZipCode zipCode) { - return zipRepo.findById(zipCode.getZip()) + return getById(zipCode.getZip()) .onItem() .ifNull() - .switchTo(createZipCode(zipCode)); + .switchTo(createZipCode(zipCode)) + .onFailure(PersistenceException.class) + .recoverWithUni(() -> getById(zipCode.getZip())); + } + + private Uni getById(String zipCode) { + return zipRepo.findById(zipCode); } private Uni createZipCode(ZipCode zipCode) { diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties index 918a129500..7c1bee8da5 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties @@ -1,9 +1,12 @@ -quarkus.datasource.db-kind=postgresql -quarkus.datasource.username=postgres -quarkus.datasource.password=example +quarkus.datasource.db-kind=mysql +quarkus.datasource.username=root +quarkus.datasource.password=root -quarkus.datasource.reactive.url=${DB_URL:postgresql://localhost:5432/postgres} -quarkus.datasource.reactive.max-size=20 +quarkus.datasource.reactive.url=${DB_URL:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true} +quarkus.datasource.reactive.max-size=95 +quarkus.datasource.reactive.mysql.ssl-mode=required #quarkus.hibernate-orm.log.sql=true quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.native.enable-vm-inspection=true +quarkus.datasource.reactive.trust-all=true \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh b/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh new file mode 100755 index 0000000000..d8e131d244 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ "$1" = "native" ]; then + mvn clean package -DskipTests spring-boot:build-image -Pnative -f $SCRIPTPATH/pom.xml +elif [ "$1" = "local-native" ]; then + mvn clean package -DskipTests -Plocal-native -f $SCRIPTPATH/pom.xml +else + mvn clean package -DskipTests spring-boot:build-image -f $SCRIPTPATH/pom.xml +fi \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh b/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh deleted file mode 100644 index c7ee730ec7..0000000000 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" - -docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.jvm -t spring-project:0.1-SNAPSHOT $SCRIPTPATH/. - diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml b/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml index 7f0fa4c8c6..408c223e9f 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml @@ -10,8 +10,8 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0 - + 2.6.9 + @@ -29,14 +29,9 @@ ${spring-native.version} - io.r2dbc - r2dbc-postgresql - runtime - - - org.postgresql - postgresql - runtime + com.github.jasync-sql + jasync-r2dbc-mysql + 2.0.8 org.springframework.boot @@ -48,131 +43,210 @@ reactor-test test + + + org.testcontainers + testcontainers + test + + + + org.testcontainers + r2dbc + test + + + + org.testcontainers + mysql + test + + + + mysql + mysql-connector-java + test + + + + org.testcontainers + junit-jupiter + test + + + + + + org.testcontainers + testcontainers-bom + 1.17.2 + pom + import + + + + org.springframework.boot spring-boot-maven-plugin - ${repackage.classifier} + exec + + true + paketobuildpacks/builder:tiny - true + false + true - org.springframework.experimental - spring-aot-maven-plugin - ${spring-native.version} - - - test-generate - - test-generate - - - - generate - - generate - - - + maven-surefire-plugin + ${surefire-plugin.version} + + + **/*IT + + - spring-releases - Spring Releases + spring-release + Spring release https://repo.spring.io/release - - false - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - spring-releases - Spring Releases + spring-release + Spring release https://repo.spring.io/release - - false - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - native - - exec - 0.9.3 - - org.graalvm.buildtools - junit-platform-native - ${native-buildtools.version} + org.junit.platform + junit-platform-launcher + test + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + paketobuildpacks/builder:tiny + + true + true + + + + + + org.springframework.experimental + spring-aot-maven-plugin + + + test-generate + + test-generate + + + + generate + + generate + + + + + + + + + local-native + + exec + 0.9.11 + + + + org.junit.platform + junit-platform-launcher + test + + + + + + org.springframework.experimental + spring-aot-maven-plugin + + + test-generate + + test-generate + + + + generate + + generate + + + + org.graalvm.buildtools native-maven-plugin ${native-buildtools.version} + true -H:+AllowVMInspection - - test-native - test - - test - - build-native - package build + package + + + test-native + + test + + test org.apache.maven.plugins maven-surefire-plugin + 3.0.0-M6 -DspringAot=true -agentlib:native-image-agent=access-filter-file=src/test/resources/access-filter.json,config-merge-dir=target/classes/META-INF/native-image @@ -185,9 +259,8 @@ 11 - - 0.11.0-RC1 - 2.17.1 + 0.12.1 + 3.0.0-M6 - \ No newline at end of file + diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm deleted file mode 100644 index ca3f3cca76..0000000000 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm +++ /dev/null @@ -1,12 +0,0 @@ -FROM openjdk:11 - -ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' - -COPY --chown=1001 target/spring-project-0.1-SNAPSHOT-exec.jar /spring-app/ - -WORKDIR /spring-app - -EXPOSE 8080 -USER 1001 - -ENTRYPOINT ["java", "-jar", "spring-project-0.1-SNAPSHOT-exec.jar" ] \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml index 2214e0a898..347b5dfe2f 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml @@ -2,21 +2,25 @@ version: '3.1' services: db: - image: postgres + image: mysql:5.7.38 ports: - - '5432:5432' + - '3306:3306' environment: - POSTGRES_PASSWORD: example + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: baeldung + command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ] + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD app: - image: spring-project:0.1-SNAPSHOT - ports: - - '8080:8080' + image: docker.io/library/spring-project:0.1-SNAPSHOT + network_mode: "host" environment: - DB_URL: r2dbc:postgresql://db:5432/postgres - links: - - "db" + DB_URL: r2dbc:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true + HOST_HOSTNAME: ${EXTERNAL_IP} depends_on: - - "db" -networks: - default: - driver: bridge \ No newline at end of file + db: + condition: service_healthy + deploy: + resources: + limits: + cpus: '3.00' diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java index 48cf7e8ed1..e8544da8db 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java @@ -1,21 +1,22 @@ package com.baeldung.spring_project; -import com.baeldung.spring_project.domain.ZIPRepo; import io.r2dbc.spi.ConnectionFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ByteArrayResource; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.r2dbc.connection.R2dbcTransactionManager; import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; import org.springframework.transaction.ReactiveTransactionManager; @SpringBootApplication +@EnableR2dbcRepositories public class Startup { public static void main(String[] args) { - SpringApplication.run(Startup.class, args).getBean(ZIPRepo.class).findById(""); + SpringApplication.run(Startup.class, args); } @Bean @@ -34,4 +35,5 @@ public class Startup { @Bean ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) { return new R2dbcTransactionManager(connectionFactory); } + } diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java index 263ce67e21..8d1f07b7b9 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java @@ -2,11 +2,14 @@ package com.baeldung.spring_project; import com.baeldung.spring_project.domain.ZIPRepo; import com.baeldung.spring_project.domain.ZipCode; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.r2dbc.UncategorizedR2dbcException; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.Function; import java.util.function.Supplier; @RestController @@ -21,7 +24,7 @@ public class ZipCodeApi { @GetMapping("/{zipcode}") public Mono findById(@PathVariable String zipcode) { - return zipRepo.findById(zipcode); + return getById(zipcode); } @GetMapping("/by_city") @@ -29,10 +32,23 @@ public class ZipCodeApi { return zipRepo.findByCity(city); } - @Transactional @PostMapping public Mono create(@RequestBody ZipCode zipCode) { - return zipRepo.findById(zipCode.getZip()).switchIfEmpty(Mono.defer(createZipCode(zipCode))); + return getById(zipCode.getZip()) + .switchIfEmpty(Mono.defer(createZipCode(zipCode))) + .onErrorResume(this::isKeyDuplicated, this.recoverWith(zipCode)); + } + + private Mono getById(String zipCode) { + return zipRepo.findById(zipCode); + } + + private boolean isKeyDuplicated(Throwable ex) { + return ex instanceof DataIntegrityViolationException || ex instanceof UncategorizedR2dbcException; + } + + private Function> recoverWith(ZipCode zipCode) { + return throwable -> zipRepo.findById(zipCode.getZip()); } private Supplier> createZipCode(ZipCode zipCode) { diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties index 1d49b67fda..e303baf6f6 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties @@ -1,5 +1,7 @@ -spring.r2dbc.url=${DB_URL:r2dbc:postgresql://localhost:5432/postgres} -spring.r2dbc.username=postgres -spring.r2dbc.password=example -spring.r2dbc.pool.enabled=true -spring.r2dbc.pool.maxSize=20 \ No newline at end of file +spring.r2dbc.url=${DB_URL:r2dbc:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true} +spring.r2dbc.properties.sslMode=required +spring.r2dbc.username=root +spring.r2dbc.password=root +spring.r2dbc.pool.enabled=true +spring.r2dbc.pool.maxSize=95 + diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java index 7487e5aa7f..7715fdc1d2 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java @@ -1,9 +1,20 @@ package com.baeldung.spring_project; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.springframework.boot.test.context.SpringBootTest; +import org.testcontainers.junit.jupiter.Testcontainers; -@SpringBootTest +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.r2dbc.url=r2dbc:tc:mysql:///baeldung?TC_IMAGE_TAG=5.7.34"} +) +@TestInstance(value = PER_CLASS) +@Testcontainers +@Disabled class StartupIT { @Test diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv b/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv new file mode 100644 index 0000000000..3b7016f3b5 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv @@ -0,0 +1,136 @@ +Holtsville +Adjuntas +Aguada +Aguadilla +Maricao +Anasco +Angeles +Arecibo +Bajadero +Barceloneta +Boqueron +Cabo Rojo +Penuelas +Camuy +Castaner +Rosario +Sabana Grande +Ciales +Utuado +Dorado +Ensenada +Florida +Garrochales +Guanica +Guayanilla +Hatillo +Hormigueros +Isabela +Jayuya +Lajas +Lares +Las Marias +Manati +Moca +Rincon +Quebradillas +Mayaguez +San German +San Sebastian +Morovis +Sabana Hoyos +San Antonio +Vega Alta +Vega Baja +Yauco +Aguas Buenas +Aguirre +Aibonito +Maunabo +Arroyo +Mercedita +Ponce +Naguabo +Naranjito +Orocovis +Palmer +Patillas +Caguas +Canovanas +Ceiba +Cayey +Fajardo +Cidra +Puerto Real +Punta Santiago +Roosevelt Roads +Rio Blanco +Rio Grande +Salinas +San Lorenzo +Santa Isabel +Vieques +Villalba +Yabucoa +Coamo +Las Piedras +Loiza +Luquillo +Culebra +Juncos +Gurabo +Coto Laurel +Comerio +Corozal +Guayama +La Plata +Humacao +Barranquitas +Juana Diaz +St Thomas +Christiansted +St John +Frederiksted +Kingshill +San Juan +Fort Buchanan +Toa Baja +Sabana Seca +Toa Alta +Bayamon +Catano +Guaynabo +Trujillo Alto +Saint Just +Carolina +Agawam +Amherst +Barre +Belchertown +Blandford +Bondsville +Brimfield +Chester +Chesterfield +Chicopee +Cummington +Easthampton +East Longmeadow +East Otis +Feeding Hills +Gilbertville +Goshen +Granby +Granville +Hadley +Hampden +Hardwick +Hatfield +Haydenville +Holyoke +Huntington +Leeds +Leverett +Ludlow +Monson +North Amherst \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua b/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua new file mode 100644 index 0000000000..a1973072d9 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua @@ -0,0 +1,65 @@ +local require = require +local json = require "json" + +math.randomseed(os.time()) + +-- read csv lines +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = { zip=values[1], type=values[2], city=values[4], state=values[7], county=values[8], timezone=values[9] } + count = count + 1 + end + + return data +end + +generator = function() + local data = loadFile() + return coroutine.create(function() + for k,v in pairs(data) do + coroutine.yield(json.stringify(v)) + end + end) +end + +return generator() \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua b/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua new file mode 100644 index 0000000000..b101e3bbda --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua @@ -0,0 +1,79 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "cities.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = values[1] + count = count + 1 + end + + return data +end + +local data = loadFile() + +local urlencode = function (str) + str = string.gsub (str, "([^0-9a-zA-Z !'()*._~-])", -- locale independent + function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub (str, " ", "+") + return str +end + +request = function() + url_path = "/zipcode/by_city?city=" .. urlencode(data[math.random(1, 136)]) + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + + return wrk.format("GET", url_path, headers, nil) +end + +done = function(summary, latency, requests) + io.write("--------------GET CITY ZIPCODES----------------\n") + for _, p in pairs({ 50, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("-----------------------------------------------\n\n") +end \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua b/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua new file mode 100644 index 0000000000..f2abf607c2 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua @@ -0,0 +1,77 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = values[1] + count = count + 1 + end + + return data +end + +local data = loadFile() + +request = function() + + local value = data[math.random(1, 12079)] + + url_path = "/zipcode/" .. value + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + + return wrk.format("GET", url_path, headers, nil) +end + + +done = function(summary, latency, requests) + io.write("--------------GET ZIPCODE----------------\n") + for _, p in pairs({ 50, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("-----------------------------------------\n\n") +end diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/json.lua b/quarkus-modules/quarkus-vs-springboot/wrk/json.lua new file mode 100644 index 0000000000..6f023a9294 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/json.lua @@ -0,0 +1,133 @@ +local json = {} + +local function kind_of(obj) + if type(obj) ~= 'table' then return type(obj) end + local i = 1 + for _ in pairs(obj) do + if obj[i] ~= nil then i = i + 1 else return 'table' end + end + if i == 1 then return 'table' else return 'array' end +end + +local function escape_str(s) + local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} + local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} + for i, c in ipairs(in_char) do + s = s:gsub(c, '\\' .. out_char[i]) + end + return s +end + +local function skip_delim(str, pos, delim, err_if_missing) + pos = pos + #str:match('^%s*', pos) + if str:sub(pos, pos) ~= delim then + if err_if_missing then + error('Expected ' .. delim .. ' near position ' .. pos) + end + return pos, false + end + return pos + 1, true +end + +local function parse_str_val(str, pos, val) + val = val or '' + local early_end_error = 'End of input found while parsing string.' + if pos > #str then error(early_end_error) end + local c = str:sub(pos, pos) + if c == '"' then return val, pos + 1 end + if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end + local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} + local nextc = str:sub(pos + 1, pos + 1) + if not nextc then error(early_end_error) end + return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) +end + +local function parse_num_val(str, pos) + local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) + local val = tonumber(num_str) + if not val then error('Error parsing number at position ' .. pos .. '.') end + return val, pos + #num_str +end + +function json.stringify(obj, as_key) + local s = {} + local kind = kind_of(obj) + if kind == 'array' then + if as_key then error('Can\'t encode array as key.') end + s[#s + 1] = '[' + for i, val in ipairs(obj) do + if i > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(val) + end + s[#s + 1] = ']' + elseif kind == 'table' then + if as_key then error('Can\'t encode table as key.') end + s[#s + 1] = '{' + for k, v in pairs(obj) do + if #s > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(k, true) + s[#s + 1] = ':' + s[#s + 1] = json.stringify(v) + end + s[#s + 1] = '}' + elseif kind == 'string' then + return '"' .. escape_str(obj) .. '"' + elseif kind == 'number' then + if as_key then return '"' .. tostring(obj) .. '"' end + return tostring(obj) + elseif kind == 'boolean' then + return tostring(obj) + elseif kind == 'nil' then + return 'null' + else + error('Unjsonifiable type: ' .. kind .. '.') + end + return table.concat(s) +end + +json.null = {} + +function json.parse(str, pos, end_delim) + pos = pos or 1 + if pos > #str then error('Reached unexpected end of input.') end + local pos = pos + #str:match('^%s*', pos) + local first = str:sub(pos, pos) + if first == '{' then + local obj, key, delim_found = {}, true, true + pos = pos + 1 + while true do + key, pos = json.parse(str, pos, '}') + if key == nil then return obj, pos end + if not delim_found then error('Comma missing between object items.') end + pos = skip_delim(str, pos, ':', true) + obj[key], pos = json.parse(str, pos) + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '[' then + local arr, val, delim_found = {}, true, true + pos = pos + 1 + while true do + val, pos = json.parse(str, pos, ']') + if val == nil then return arr, pos end + if not delim_found then error('Comma missing between array items.') end + arr[#arr + 1] = val + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '"' then + return parse_str_val(str, pos + 1) + elseif first == '-' or first:match('%d') then + return parse_num_val(str, pos) + elseif first == end_delim then + return nil, pos + 1 + else + local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} + for lit_str, lit_val in pairs(literals) do + local lit_end = pos + #lit_str - 1 + if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end + end + local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) + error('Invalid json syntax starting at ' .. pos_info_str) + end +end + +return json diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua b/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua new file mode 100644 index 0000000000..b8e60da015 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua @@ -0,0 +1,73 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = { zip=values[1], type=values[2], city=values[4], state=values[7], county=values[8], timezone=values[9] } + count = count + 1 + end + + return data +end + +local data = loadFile() + +request = function() + local url_path = "/zipcode" + local val = data[math.random(1, 12079)] + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + return wrk.format("POST", url_path, headers, json.stringify(val)) +end + +done = function(summary, latency, requests) + io.write("--------------POST ZIPCODE----------------\n") + for _, p in pairs({ 50, 75, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("------------------------------------------\n\n") +end \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh b/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh new file mode 100755 index 0000000000..0d565d0688 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +$wrk_home/wrk -t1 -c5 -d1m -s ./post_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 & sleep 60 + +$wrk_home/wrk -t1 -c20 -d5m -s ./post_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 & sleep 60 + +$wrk_home/wrk -t1 -c20 -d5m -s ./get_by_city.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & +$wrk_home/wrk -t1 -c20 -d5m -s ./get_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & sleep 120 + +$wrk_home/wrk -t2 -c10 -d3m -s ./get_by_city.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & +$wrk_home/wrk -t2 -c10 -d3m -s ./get_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & + +wait + From 858e15868027f4a975527965a58b6a7ac34a49e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matei=20Cern=C4=83ianu?= <34006140+matei-cernaianu@users.noreply.github.com> Date: Sun, 31 Jul 2022 02:18:52 +0300 Subject: [PATCH 41/42] BAEL-5651: How to check if an integer is in a given range? (#12470) * BAEL-5651: How to check if an integer is in a given range? * BAEL-5651: How to check if an integer is in a given range? --- core-java-modules/core-java-numbers-5/pom.xml | 18 ++++ .../intrange/IntRangeApacheCommons.java | 26 +++++ .../intrange/IntRangeGoogleGuava.java | 26 +++++ .../baeldung/intrange/IntRangeOperators.java | 20 ++++ .../baeldung/intrange/IntRangeValueRange.java | 26 +++++ .../IntRangeApacheCommonsUnitTest.java | 97 +++++++++++++++++++ .../intrange/IntRangeGoogleGuavaUnitTest.java | 87 +++++++++++++++++ .../intrange/IntRangeOperatorsUnitTest.java | 87 +++++++++++++++++ .../intrange/IntRangeValueRangeUnitTest.java | 97 +++++++++++++++++++ 9 files changed, 484 insertions(+) create mode 100644 core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeApacheCommons.java create mode 100644 core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeGoogleGuava.java create mode 100644 core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeOperators.java create mode 100644 core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeValueRange.java create mode 100644 core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeApacheCommonsUnitTest.java create mode 100644 core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeGoogleGuavaUnitTest.java create mode 100644 core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeOperatorsUnitTest.java create mode 100644 core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeValueRangeUnitTest.java diff --git a/core-java-modules/core-java-numbers-5/pom.xml b/core-java-modules/core-java-numbers-5/pom.xml index f236d28ccb..bab1e4d622 100644 --- a/core-java-modules/core-java-numbers-5/pom.xml +++ b/core-java-modules/core-java-numbers-5/pom.xml @@ -22,4 +22,22 @@ + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.google.guava + guava + ${guava.version} + + \ No newline at end of file diff --git a/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeApacheCommons.java b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeApacheCommons.java new file mode 100644 index 0000000000..0a82f934a7 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeApacheCommons.java @@ -0,0 +1,26 @@ +package com.baeldung.intrange; + +import org.apache.commons.lang3.Range; + +public class IntRangeApacheCommons { + + public static boolean isInClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.between(lowerBound, upperBound); + return range.contains(number); + } + + public static boolean isInOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.between(lowerBound + 1, upperBound - 1); + return range.contains(number); + } + + public static boolean isInOpenClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.between(lowerBound + 1, upperBound); + return range.contains(number); + } + + public static boolean isInClosedOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.between(lowerBound, upperBound - 1); + return range.contains(number); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeGoogleGuava.java b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeGoogleGuava.java new file mode 100644 index 0000000000..d4cadfa050 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeGoogleGuava.java @@ -0,0 +1,26 @@ +package com.baeldung.intrange; + +import com.google.common.collect.Range; + +public class IntRangeGoogleGuava { + + public static boolean isInClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.closed(lowerBound, upperBound); + return range.contains(number); + } + + public static boolean isInOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.open(lowerBound, upperBound); + return range.contains(number); + } + + public static boolean isInOpenClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.openClosed(lowerBound, upperBound); + return range.contains(number); + } + + public static boolean isInClosedOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final Range range = Range.closedOpen(lowerBound, upperBound); + return range.contains(number); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeOperators.java b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeOperators.java new file mode 100644 index 0000000000..77e32161e0 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeOperators.java @@ -0,0 +1,20 @@ +package com.baeldung.intrange; + +public class IntRangeOperators { + + public static boolean isInClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + return (lowerBound <= number && number <= upperBound); + } + + public static boolean isInOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + return (lowerBound < number && number < upperBound); + } + + public static boolean isInOpenClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + return (lowerBound < number && number <= upperBound); + } + + public static boolean isInClosedOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + return (lowerBound <= number && number < upperBound); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeValueRange.java b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeValueRange.java new file mode 100644 index 0000000000..cdce555341 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/main/java/com/baeldung/intrange/IntRangeValueRange.java @@ -0,0 +1,26 @@ +package com.baeldung.intrange; + +import java.time.temporal.ValueRange; + +public class IntRangeValueRange { + + public static boolean isInClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final ValueRange range = ValueRange.of(lowerBound, upperBound); + return range.isValidIntValue(number); + } + + public static boolean isInOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final ValueRange range = ValueRange.of(lowerBound + 1, upperBound - 1); + return range.isValidIntValue(number); + } + + public static boolean isInOpenClosedRange(Integer number, Integer lowerBound, Integer upperBound) { + final ValueRange range = ValueRange.of(lowerBound + 1, upperBound); + return range.isValidIntValue(number); + } + + public static boolean isInClosedOpenRange(Integer number, Integer lowerBound, Integer upperBound) { + final ValueRange range = ValueRange.of(lowerBound, upperBound - 1); + return range.isValidIntValue(number); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeApacheCommonsUnitTest.java b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeApacheCommonsUnitTest.java new file mode 100644 index 0000000000..d3b15d0e42 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeApacheCommonsUnitTest.java @@ -0,0 +1,97 @@ +package com.baeldung.intrange; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class IntRangeApacheCommonsUnitTest { + + @Test + void givenIntRangeApacheCommons_whenIsInClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInClosedRange(10, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInClosedRange(20, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsNotInClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInClosedRange(8, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInClosedRange(22, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsInOpenRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInOpenRange(11, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInOpenRange(19, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsNotInOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsInOpenClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInOpenClosedRange(11, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInOpenClosedRange(20, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsNotInOpenClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInOpenClosedRange(10, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInOpenClosedRange(21, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsInClosedOpenRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInClosedOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInClosedOpenRange(19, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeApacheCommons_whenIsNotInClosedOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeApacheCommons.isInClosedOpenRange(9, 10, 20); + boolean resultUpperBound = IntRangeApacheCommons.isInClosedOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeGoogleGuavaUnitTest.java b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeGoogleGuavaUnitTest.java new file mode 100644 index 0000000000..9abdc20d31 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeGoogleGuavaUnitTest.java @@ -0,0 +1,87 @@ +package com.baeldung.intrange; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class IntRangeGoogleGuavaUnitTest { + + @Test + void givenIntRangeGoogleGuava_whenIsInOpenRange_thenSuccess() { + // when + boolean result = IntRangeGoogleGuava.isInOpenRange(14, 10, 20); + + //then + assertTrue(result); + } + + @Test + void givenIntRangeGoogleGuava_whenIsNotInOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeGoogleGuava.isInOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeGoogleGuava.isInOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeGoogleGuava_whenIsInClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeGoogleGuava.isInClosedRange(-10, -10, 5); + boolean resultUpperBound = IntRangeGoogleGuava.isInClosedRange(5, -10, 5); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeGoogleGuava_whenIsNotInClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeGoogleGuava.isInClosedRange(-11, -10, 5); + boolean resultUpperBound = IntRangeGoogleGuava.isInClosedRange(6, -10, 5); + + //then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeGoogleGuava_whenIsInOpenClosedRange_thenSuccess() { + // when + boolean result = IntRangeGoogleGuava.isInOpenClosedRange(20, 10, 20); + + // then + assertTrue(result); + } + + @Test + void givenIntRangeGoogleGuava_whenIsNotInOpenClosedRange_thenFailure() { + // when + boolean result = IntRangeGoogleGuava.isInOpenClosedRange(10, 10, 20); + + // then + assertFalse(result); + } + + @Test + void givenIntRangeGoogleGuava_whenIsInClosedOpenRange_thenSuccess() { + // when + boolean result = IntRangeGoogleGuava.isInClosedOpenRange(10, 10, 20); + + // then + assertTrue(result); + } + + @Test + void givenIntRangeGoogleGuava_whenIsNotInClosedOpenRange_thenFailure() { + // when + boolean result = IntRangeGoogleGuava.isInClosedOpenRange(20, 10, 20); + + // then + assertFalse(result); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeOperatorsUnitTest.java b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeOperatorsUnitTest.java new file mode 100644 index 0000000000..76abeb1ee3 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeOperatorsUnitTest.java @@ -0,0 +1,87 @@ +package com.baeldung.intrange; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class IntRangeOperatorsUnitTest { + + @Test + void givenIntRangeOperators_whenIsInOpenRange_thenSuccess() { + // when + boolean result = IntRangeOperators.isInOpenRange(11, 10, 20); + + //then + assertTrue(result); + } + + @Test + void givenIntRangeOperators_whenIsNotInOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeOperators.isInOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeOperators.isInOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeOperators_whenIsInClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeOperators.isInClosedRange(-10, -10, 5); + boolean resultUpperBound = IntRangeOperators.isInClosedRange(5, -10, 5); + + // then + assertTrue(resultUpperBound); + assertTrue(resultLowerBound); + } + + @Test + void givenIntRangeOperators_whenIsNotInClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeOperators.isInClosedRange(-11, -10, 5); + boolean resultUpperBound = IntRangeOperators.isInClosedRange(6, -10, 5); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeOperators_whenIsInOpenClosedRange_thenSuccess() { + // when + boolean result = IntRangeOperators.isInOpenClosedRange(20, 10, 20); + + // then + assertTrue(result); + } + + @Test + void givenIntRangeOperators_whenIsNotInOpenClosedRange_thenFailure() { + // when + boolean result = IntRangeOperators.isInOpenClosedRange(10, 10, 20); + + // then + assertFalse(result); + } + + @Test + void givenIntRangeOperators_whenIsInClosedOpenRange_thenSuccess() { + // when + boolean result = IntRangeOperators.isInClosedOpenRange(10, 10, 20); + + // then + assertTrue(result); + } + + @Test + void givenIntRangeOperators_whenIsNotInClosedOpenRange_thenFailure() { + // when + boolean result = IntRangeOperators.isInClosedOpenRange(20, 10, 20); + + // then + assertFalse(result); + } +} diff --git a/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeValueRangeUnitTest.java b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeValueRangeUnitTest.java new file mode 100644 index 0000000000..dc2105c675 --- /dev/null +++ b/core-java-modules/core-java-numbers-5/src/test/java/com/baeldung/intrange/IntRangeValueRangeUnitTest.java @@ -0,0 +1,97 @@ +package com.baeldung.intrange; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class IntRangeValueRangeUnitTest { + + @Test + void givenIntRangeValueRange_whenIsInClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeValueRange.isInClosedRange(10, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInClosedRange(20, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsNotInClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeValueRange.isInClosedRange(9, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInClosedRange(21, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsInOpenRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeValueRange.isInOpenRange(11, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInOpenRange(19, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsNotInOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeValueRange.isInOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsInOpenClosedRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeValueRange.isInOpenClosedRange(11, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInOpenClosedRange(20, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsNotInOpenClosedRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeValueRange.isInOpenClosedRange(10, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInOpenClosedRange(21, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsInClosedOpenRange_thenSuccess() { + // when + boolean resultLowerBound = IntRangeValueRange.isInClosedOpenRange(10, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInClosedOpenRange(19, 10, 20); + + // then + assertTrue(resultLowerBound); + assertTrue(resultUpperBound); + } + + @Test + void givenIntRangeValueRange_whenIsNotInClosedOpenRange_thenFailure() { + // when + boolean resultLowerBound = IntRangeValueRange.isInClosedOpenRange(9, 10, 20); + boolean resultUpperBound = IntRangeValueRange.isInClosedOpenRange(20, 10, 20); + + // then + assertFalse(resultLowerBound); + assertFalse(resultUpperBound); + } +} From 0ec6a036ba38122a9d092fe6f9de0db4eb4a189a Mon Sep 17 00:00:00 2001 From: lucaCambi77 Date: Sun, 31 Jul 2022 04:43:09 +0200 Subject: [PATCH 42/42] Spring Security: Upgrading the deprecated WebSecurityConfigurerAdapter (#12540) * add SecurityFilterChain application in spring security web boot 4 * pmd violation * fix: pom description * remove unused code, format * make tests grouped logically * add missing case for user role * rename package to lower case --- spring-security-modules/pom.xml | 1 + .../spring-security-web-boot-4/README.md | 10 ++ .../spring-security-web-boot-4/pom.xml | 39 ++++++++ .../SecurityFilterChainApplication.java | 14 +++ .../configuration/SecurityConfig.java | 50 ++++++++++ .../UserDetailServiceConfig.java | 31 ++++++ .../controller/ResourceController.java | 34 +++++++ .../SecurityFilterChainIntegrationTest.java | 94 +++++++++++++++++++ .../src/test/resources/application.properties | 1 + .../src/test/resources/logback-test.xml | 12 +++ 10 files changed, 286 insertions(+) create mode 100644 spring-security-modules/spring-security-web-boot-4/README.md create mode 100644 spring-security-modules/spring-security-web-boot-4/pom.xml create mode 100644 spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java create mode 100644 spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java create mode 100644 spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java create mode 100644 spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java create mode 100644 spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java create mode 100644 spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties create mode 100644 spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 2bd6d23058..83412d2252 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -32,6 +32,7 @@ spring-security-web-boot-1 spring-security-web-boot-2 spring-security-web-boot-3 + spring-security-web-boot-4 spring-security-web-digest-auth spring-security-web-login spring-security-web-login-2 diff --git a/spring-security-modules/spring-security-web-boot-4/README.md b/spring-security-modules/spring-security-web-boot-4/README.md new file mode 100644 index 0000000000..0856315682 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/README.md @@ -0,0 +1,10 @@ +## Spring Boot Security MVC + +This module contains articles about Spring Security with Spring MVC in Boot applications + +### The Course +The "REST With Spring" Classes: http://github.learnspringsecurity.com + +### Relevant Articles: + +- More articles: [[<-- prev]](/spring-security-modules/spring-security-web-boot-3) diff --git a/spring-security-modules/spring-security-web-boot-4/pom.xml b/spring-security-modules/spring-security-web-boot-4/pom.xml new file mode 100644 index 0000000000..8dd56e1de2 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + spring-security-web-boot-4 + 0.0.1-SNAPSHOT + spring-security-web-boot-4 + jar + Spring Security MVC Boot - 4 + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java new file mode 100644 index 0000000000..86f98b651b --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java @@ -0,0 +1,14 @@ +package com.baeldung.securityfilterchain; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@SpringBootApplication +@EnableWebMvc +public class SecurityFilterChainApplication { + + public static void main(String[] args) { + SpringApplication.run(SecurityFilterChainApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java new file mode 100644 index 0000000000..4d3bec2ad2 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java @@ -0,0 +1,50 @@ +package com.baeldung.securityfilterchain.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) +public class SecurityConfig { + + @Value("${spring.security.debug:false}") + boolean securityDebug; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeRequests() + .antMatchers(HttpMethod.DELETE) + .hasRole("ADMIN") + .antMatchers("/admin/**") + .hasAnyRole("ADMIN") + .antMatchers("/user/**") + .hasAnyRole("USER", "ADMIN") + .antMatchers("/login/**") + .anonymous() + .anyRequest() + .authenticated() + .and() + .httpBasic() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + return http.build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.debug(securityDebug) + .ignoring() + .antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico"); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java new file mode 100644 index 0000000000..6a614e888b --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java @@ -0,0 +1,31 @@ +package com.baeldung.securityfilterchain.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +@Configuration +public class UserDetailServiceConfig { + + @Bean + public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user") + .password(bCryptPasswordEncoder.encode("userPass")) + .roles("USER") + .build()); + manager.createUser(User.withUsername("admin") + .password(bCryptPasswordEncoder.encode("adminPass")) + .roles("ADMIN", "USER") + .build()); + return manager; + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java new file mode 100644 index 0000000000..e01d4ae9b3 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java @@ -0,0 +1,34 @@ +package com.baeldung.securityfilterchain.controller; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ResourceController { + @GetMapping("/login") + public String loginEndpoint() { + return "Login!"; + } + + @GetMapping("/admin") + public String adminEndpoint() { + return "Admin!"; + } + + @GetMapping("/user") + public String userEndpoint() { + return "User!"; + } + + @GetMapping("/all") + public String allRolesEndpoint() { + return "All Roles!"; + } + + @DeleteMapping("/delete") + public String deleteEndpoint(@RequestBody String s) { + return "I am deleting " + s; + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java b/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java new file mode 100644 index 0000000000..e94b1b2f12 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java @@ -0,0 +1,94 @@ +package com.baeldung.securityfilterchain; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest(classes = SecurityFilterChainApplication.class) +public class SecurityFilterChainIntegrationTest { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @BeforeEach + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessUserEndpoint_thenOk() throws Exception { + mvc.perform(get("/user")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessAdminSecuredEndpoint_thenIsOk() throws Exception { + mvc.perform(get("/admin")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessDeleteSecuredEndpoint_thenIsOk() throws Exception { + mvc.perform(delete("/delete").content("{}")) + .andExpect(status().isOk()); + } + + @Test + @WithAnonymousUser + public void whenAnonymousAccessLogin_thenOk() throws Exception { + mvc.perform(get("/login")) + .andExpect(status().isOk()); + } + + @Test + @WithAnonymousUser + public void whenAnonymousAccessRestrictedEndpoint_thenIsUnauthorized() throws Exception { + mvc.perform(get("/all")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithUserDetails() + public void whenUserAccessUserSecuredEndpoint_thenOk() throws Exception { + mvc.perform(get("/user")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails() + public void whenUserAccessRestrictedEndpoint_thenOk() throws Exception { + mvc.perform(get("/all")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails() + public void whenUserAccessAdminSecuredEndpoint_thenIsForbidden() throws Exception { + mvc.perform(get("/admin")) + .andExpect(status().isForbidden()); + } + + @Test + @WithUserDetails() + public void whenUserAccessDeleteSecuredEndpoint_thenIsForbidden() throws Exception { + mvc.perform(delete("/delete")) + .andExpect(status().isForbidden()); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties b/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties new file mode 100644 index 0000000000..090ff54e92 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties @@ -0,0 +1 @@ +spring.security.debug=true \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml b/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..c5a4b0ab1c --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n + + + + + + + \ No newline at end of file